diff --git a/.eslintrc.json b/.eslintrc.json index 4d49a16fbf0..af34ee181a9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -210,8 +210,7 @@ // - electron-browser "when": "hasBrowser", "allow": [ - "vs/css!./**/*", - "@microsoft/applicationinsights-web" + "vs/css!./**/*" ] }, { @@ -226,7 +225,6 @@ "@vscode/vscode-languagedetection", "@vscode/ripgrep", "@vscode/iconv-lite-umd", - "applicationinsights", "assert", "child_process", "console", @@ -537,7 +535,7 @@ ] }, { - "target": "src/vs/workbench/{workbench.sandbox.main.ts,workbench.desktop.sandbox.main.ts}", + "target": "src/vs/workbench/workbench.desktop.main.ts", "layer": "electron-sandbox", "restrictions": [ "vs/base/*/~", @@ -550,26 +548,7 @@ "vs/workbench/api/~", "vs/workbench/services/*/~", "vs/workbench/contrib/*/~", - "vs/workbench/workbench.common.main", - "vs/workbench/workbench.sandbox.main" - ] - }, - { - "target": "src/vs/workbench/workbench.desktop.main.ts", - "layer": "electron-browser", - "restrictions": [ - "vs/base/*/~", - "vs/base/parts/*/~", - "vs/platform/*/~", - "vs/editor/~", - "vs/editor/contrib/*/~", - "vs/editor/editor.all", - "vs/workbench/~", - "vs/workbench/api/~", - "vs/workbench/services/*/~", - "vs/workbench/contrib/*/~", - "vs/workbench/workbench.common.main", - "vs/workbench/workbench.sandbox.main" + "vs/workbench/workbench.common.main" ] }, { diff --git a/.github/classifier.json b/.github/classifier.json index 5b5625f6e93..9e7e6e26e17 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -145,7 +145,7 @@ "snippets": {"assign": ["jrieken"]}, "splitview": {"assign": ["joaomoreno"]}, "suggest": {"assign": ["jrieken"]}, - "tasks": {"assign": ["alexr00"], "accuracy": 0.85}, + "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, "telemetry": {"assign": []}, "themes": {"assign": ["aeschli"]}, "timeline": {"assign": ["lramos15"]}, diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index b5fe086de05..97daf8fd946 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -22,8 +22,6 @@ jobs: # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb sudo update-rc.d xvfb defaults diff --git a/.github/workflows/no-yarn-lock-changes.yml b/.github/workflows/no-yarn-lock-changes.yml index ebd735bf7e5..300a98ad998 100644 --- a/.github/workflows/no-yarn-lock-changes.yml +++ b/.github/workflows/no-yarn-lock-changes.yml @@ -18,8 +18,9 @@ jobs: run: | echo "user: ${{ github.event.pull_request.user.login }}" echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" + echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" echo "should_run: ${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" - echo "::set-output name=should_run::${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" + echo "::set-output name=should_run::${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" - name: Get file changes uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 if: ${{ steps.control.outputs.should_run == 'true' }} diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 3120d4ad8fc..6ba33aa8ae8 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"June 2022\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"July 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 48195c79000..e1a91c7b80a 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-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\r\n\r\n$MILESTONE=milestone:\"May 2022\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"June 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 4ce504b017f..342cb52b014 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-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"May 2022\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"June 2022\"\n\n$MINE=assignee:@me" }, { "kind": 1, diff --git a/.vscode/settings.json b/.vscode/settings.json index e698d02574e..71bded80a79 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,6 +73,7 @@ ], "git.branchProtectionPrompt": "alwaysCommitToNewBranch", "git.branchRandomName.enable": true, + "git.mergeEditor": true, "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" }, diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 0fca6a19272..9c691ad41e3 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -7,7 +7,7 @@ This project incorporates components from the projects listed below. The origina 1. atom/language-clojure version 0.22.8 (https://github.com/atom/language-clojure) 2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) -3. atom/language-css version 0.45.0 (https://github.com/atom/language-css) +3. atom/language-css version 0.45.1 (https://github.com/atom/language-css) 4. atom/language-java version 0.32.1 (https://github.com/atom/language-java) 5. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass) 6. atom/language-shellscript version 0.28.2 (https://github.com/atom/language-shellscript) @@ -30,50 +30,53 @@ This project incorporates components from the projects listed below. The origina 23. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 24. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 25. James-Yu/LaTeX-Workshop version 8.19.1 (https://github.com/James-Yu/LaTeX-Workshop) -26. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) -27. jeff-hykin/cpp-textmate-grammar version 1.15.6 (https://github.com/jeff-hykin/cpp-textmate-grammar) -28. jlelong/vscode-latex-basics version 1.3.0 (https://github.com/jlelong/vscode-latex-basics) -29. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) -30. JuliaEditorSupport/atom-language-julia version 0.22.1 (https://github.com/JuliaEditorSupport/atom-language-julia) -31. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) -32. language-docker (https://github.com/moby/moby) -33. language-less version 0.34.2 (https://github.com/atom/language-less) -34. language-php version 0.48.1 (https://github.com/atom/language-php) -35. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) -36. marked version 4.0.16 (https://github.com/markedjs/marked) -37. mdn-data version 1.1.12 (https://github.com/mdn/data) -38. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) -39. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) -40. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) -41. microsoft/vscode-mssql version 1.10.1 (https://github.com/microsoft/vscode-mssql) -42. mmims/language-batchfile version 0.7.6 (https://github.com/mmims/language-batchfile) -43. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) -44. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) -45. rust-syntax version 0.5.0 (https://github.com/dustypomerleau/rust-syntax) -46. semver version 5.5.0 (https://github.com/npm/node-semver) -47. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -48. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -49. sumneko/lua.tmbundle version 1.0.0 (https://github.com/sumneko/lua.tmbundle) -50. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -51. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -52. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -53. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -54. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -55. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -56. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -57. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -58. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -59. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -60. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -61. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -62. trond-snekvik/vscode-rst version 1.5.1 (https://github.com/trond-snekvik/vscode-rst) -63. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) -64. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) -65. Unicode version 12.0.0 (https://home.unicode.org/) -66. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) -67. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) -68. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -69. Web Background Synchronization (https://github.com/WICG/background-sync) +26. jeff-hykin/better-c-syntax version 1.13.2 (https://github.com/jeff-hykin/better-c-syntax) +27. jeff-hykin/better-cpp-syntax version 1.15.17 (https://github.com/jeff-hykin/better-cpp-syntax) +28. jeff-hykin/better-objc-syntax version 0.2.0 (https://github.com/jeff-hykin/better-objc-syntax) +29. jeff-hykin/better-objcpp-syntax version 0.1.0 (https://github.com/jeff-hykin/better-objcpp-syntax) +30. jlelong/vscode-latex-basics version 1.3.0 (https://github.com/jlelong/vscode-latex-basics) +31. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) +32. JuliaEditorSupport/atom-language-julia version 0.22.1 (https://github.com/JuliaEditorSupport/atom-language-julia) +33. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) +34. language-docker (https://github.com/moby/moby) +35. language-less version 0.34.2 (https://github.com/atom/language-less) +36. language-php version 0.48.1 (https://github.com/atom/language-php) +37. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) +38. marked version 4.0.16 (https://github.com/markedjs/marked) +39. mdn-data version 1.1.12 (https://github.com/mdn/data) +40. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) +41. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) +42. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) +43. microsoft/vscode-mssql version 1.10.1 (https://github.com/microsoft/vscode-mssql) +44. mmims/language-batchfile version 0.7.6 (https://github.com/mmims/language-batchfile) +45. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) +46. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) +47. rust-syntax version 0.5.0 (https://github.com/dustypomerleau/rust-syntax) +48. semver version 5.5.0 (https://github.com/npm/node-semver) +49. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +50. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +51. sumneko/lua.tmbundle version 1.0.0 (https://github.com/sumneko/lua.tmbundle) +52. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +53. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +54. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +55. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +56. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +57. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +58. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +59. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +60. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +61. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +62. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +63. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +64. trond-snekvik/vscode-rst version 1.5.1 (https://github.com/trond-snekvik/vscode-rst) +65. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) +66. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) +67. Unicode version 12.0.0 (https://home.unicode.org/) +68. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) +69. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) +70. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +71. vscode-win32-app-container-tokens (https://github.com/microsoft/vscode-win32-app-container-tokens) +72. Web Background Synchronization (https://github.com/WICG/background-sync) %% atom/language-clojure NOTICES AND INFORMATION BEGIN HERE @@ -1208,7 +1211,7 @@ SOFTWARE. ========================================= END OF James-Yu/LaTeX-Workshop NOTICES AND INFORMATION -%% jeff-hykin/cpp-textmate-grammar NOTICES AND INFORMATION BEGIN HERE +%% jeff-hykin/better-c-syntax NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -1232,7 +1235,85 @@ 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. ========================================= -END OF jeff-hykin/cpp-textmate-grammar NOTICES AND INFORMATION +END OF jeff-hykin/better-c-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-cpp-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +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. +========================================= +END OF jeff-hykin/better-cpp-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-objc-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +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. +========================================= +END OF jeff-hykin/better-objc-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-objcpp-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +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. +========================================= +END OF jeff-hykin/better-objcpp-syntax NOTICES AND INFORMATION %% jlelong/vscode-latex-basics NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -3123,6 +3204,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO ========================================= END OF vscode-swift NOTICES AND INFORMATION +%% vscode-win32-app-container-tokens NOTICES AND INFORMATION BEGIN HERE +========================================= +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 +========================================= +END OF vscode-win32-app-container-tokens NOTICES AND INFORMATION + %% Web Background Synchronization NOTICES AND INFORMATION BEGIN HERE ========================================= Apache License diff --git a/build/.cachesalt b/build/.cachesalt index 11b63081f5a..f69e77227c0 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2022-06-10T10:20:54.664Z +2022-07-07T13:42:16.836Z diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index c5547cbdcf2..4c30c5e0b31 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -46,6 +46,7 @@ steps: compile-extension:html-language-features-server \ compile-extension:ipynb \ compile-extension:json-language-features-server \ + compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ diff --git a/build/azure-pipelines/linux/product-build-linux-client-test.yml b/build/azure-pipelines/linux/product-build-linux-client-test.yml index ae1e77aaa73..bc9aae42daf 100644 --- a/build/azure-pipelines/linux/product-build-linux-client-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-client-test.yml @@ -58,6 +58,7 @@ steps: compile-extension:html-language-features-server \ compile-extension:ipynb \ compile-extension:json-language-features-server \ + compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 4b846b1bada..9dc50f8bcc4 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -52,6 +52,7 @@ steps: compile-extension:html-language-features-server ` compile-extension:ipynb ` compile-extension:json-language-features-server ` + compile-extension:markdown-language-features-server ` compile-extension:markdown-language-features ` compile-extension-media ` compile-extension:microsoft-authentication ` diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index a07071e345d..bb893f02923 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -53,6 +53,7 @@ const compilations = [ 'json-language-features/client/tsconfig.json', 'json-language-features/server/tsconfig.json', 'markdown-language-features/preview-src/tsconfig.json', + 'markdown-language-features/server/tsconfig.json', 'markdown-language-features/tsconfig.json', 'markdown-math/tsconfig.json', 'merge-conflict/tsconfig.json', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 4a47b63c823..28f8e52db81 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -76,7 +76,7 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/tasks/**/*.json', 'out-build/vs/platform/files/**/*.exe', 'out-build/vs/platform/files/**/*.md', - 'out-build/vs/code/electron-browser/workbench/**', + 'out-build/vs/code/electron-sandbox/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-sandbox/issue/issueReporter.js', 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js', @@ -165,8 +165,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', 'vs/workbench/api/node/extensionHostProcess.js', - 'vs/code/electron-browser/workbench/workbench.html', - 'vs/code/electron-browser/workbench/workbench.js' + 'vs/code/electron-sandbox/workbench/workbench.html', + 'vs/code/electron-sandbox/workbench/workbench.js' ]); const src = gulp.src(out + '/**', { base: '.' }) diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 7d0f70f9bef..210556eddb6 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -210,7 +210,7 @@ function buildRpmPackage(arch) { return shell.task([ 'mkdir -p ' + destination, - 'HOME="$(pwd)/' + destination + '" fakeroot rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, + 'HOME="$(pwd)/' + destination + '" rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, 'cp "' + rpmOut + '/$(ls ' + rpmOut + ')" ' + destination + '/' ]); } diff --git a/build/lib/i18n.js b/build/lib/i18n.js index dc7c12459b7..13de16ce9a0 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -599,17 +599,24 @@ function createXlfFilesForExtensions() { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys = []; + const messages = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 9f39f658de2..2ab4a471fb6 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -279,7 +279,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/sessionSync", + "name": "vs/workbench/contrib/editSessions", "project": "vscode-workbench" }, { @@ -431,7 +431,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/services/sessionSync", + "name": "vs/workbench/services/editSessions", "project": "vscode-workbench" }, { diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index dae24b53679..05b0634120b 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -714,15 +714,22 @@ export function createXlfFilesForExtensions(): ThroughStream { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json: PackageJsonFormat = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys: Array = []; + const messages: string[] = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index c83d6fcf984..2580ebe4e3a 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -72,6 +72,11 @@ const RULES = [ target: '**/vs/**/test/**', skip: true // -> skip all test files }, + // TODO@bpasero remove me once electron utility process has landed + { + target: '**/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts', + skip: true + }, // Common: vs/base/common/platform.ts { target: '**/vs/base/common/platform.ts', diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 95810c3b5f4..7e93c1413b0 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -77,6 +77,12 @@ const RULES: IRule[] = [ skip: true // -> skip all test files }, + // TODO@bpasero remove me once electron utility process has landed + { + target: '**/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts', + skip: true + }, + // Common: vs/base/common/platform.ts { target: '**/vs/base/common/platform.ts', diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 7da255d529f..38e6b5c00e4 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -121,20 +121,29 @@ class Transpiler { this._allJobs = []; logFn('Transpile', `will use ${Transpiler.P} transpile worker`); this._getOutputFileName = (file) => { - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; + try { + // windows: path-sep normalizing + file = ts.normalizePath(file); + if (!_cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + _cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + _cmdLine.fileNames.push(file); + } + const outfile = ts.getOutputFileNames(_cmdLine, file, true)[0]; + if (isDts) { + _cmdLine.fileNames.pop(); + } + return outfile; } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); + catch (err) { + console.error(file, _cmdLine.fileNames); + console.error(err); + throw new err; } - const outfile = ts.getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; }; } async join() { diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 3afc3547dd5..7bd789ef6eb 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -166,23 +166,35 @@ export class Transpiler { // very complicated logic to re-use TS internal functions to know the output path // given a TS input path and its config type InternalTsApi = typeof ts & { + normalizePath(path: string): string; getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; }; this._getOutputFileName = (file) => { - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; + try { + + // windows: path-sep normalizing + file = (ts).normalizePath(file); + + if (!_cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + _cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + _cmdLine.fileNames.push(file); + } + const outfile = (ts).getOutputFileNames(_cmdLine, file, true)[0]; + if (isDts) { + _cmdLine.fileNames.pop(); + } + return outfile; + + } catch (err) { + console.error(file, _cmdLine.fileNames); + console.error(err); + throw new err; } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); - } - const outfile = (ts).getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; }; } diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index 0896b1bb6ca..02886a53b12 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -74,7 +74,6 @@ exports.referenceGeneratedDepsByArch = { 'libgbm.so.1()(64bit)', 'libgcc_s.so.1()(64bit)', 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -157,7 +156,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -247,7 +245,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 8ef34d0320f..012755a04c5 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -74,7 +74,6 @@ export const referenceGeneratedDepsByArch = { 'libgbm.so.1()(64bit)', 'libgcc_s.so.1()(64bit)', 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -157,7 +156,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -247,7 +245,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 35521822dc6..d77e0099290 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -28,6 +28,7 @@ exports.dirs = [ 'extensions/jake', 'extensions/json-language-features', 'extensions/json-language-features/server', + 'extensions/markdown-language-features/server', 'extensions/markdown-language-features', 'extensions/markdown-math', 'extensions/merge-conflict', diff --git a/build/package.json b/build/package.json index c259ad65063..3b46323c250 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,6 @@ "@typescript-eslint/experimental-utils": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", "@vscode/iconv-lite-umd": "0.7.0", - "applicationinsights": "1.4.2", "byline": "^5.0.0", "colors": "^1.4.0", "commander": "^7.0.0", @@ -52,7 +51,7 @@ "esbuild": "^0.14.2", "extract-zip": "^2.0.1", "fs-extra": "^9.1.0", - "got": "11.8.1", + "got": "11.8.5", "gulp-merge-json": "^2.1.1", "gulp-shell": "^0.8.0", "jsonc-parser": "^2.3.0", diff --git a/build/yarn.lock b/build/yarn.lock index d65a62ebce3..4ea17c7f5d4 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -847,16 +847,6 @@ anymatch@^3.0.0, anymatch@^3.1.1, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -applicationinsights@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" - integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -909,21 +899,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" - -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1067,17 +1042,17 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^4.0.0" lowercase-keys "^2.0.0" - normalize-url "^4.1.0" + normalize-url "^6.0.1" responselike "^2.0.0" call-bind@^1.0.0: @@ -1185,15 +1160,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== - dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1290,14 +1256,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - core-js@^3.6.5: version "3.15.2" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" @@ -1427,18 +1385,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -diagnostic-channel-publishers@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" - integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc= - dependencies: - semver "^5.3.0" - dir-compare@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" @@ -1510,13 +1456,6 @@ electron-osx-sign@^0.4.16: minimist "^1.2.0" plist "^3.0.1" -emitter-listener@^1.0.1, emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1974,17 +1913,17 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -got@11.8.1: - version "11.8.1" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" - integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== +got@11.8.5: + version "11.8.5" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" + integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" @@ -2604,6 +2543,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -3006,7 +2950,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver@^5.1.0, semver@^5.3.0: +semver@^5.1.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== @@ -3059,11 +3003,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -3117,11 +3056,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - stoppable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" diff --git a/cglicenses.json b/cglicenses.json index e969eb826e9..4ce1e015e46 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -147,5 +147,31 @@ "", "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." ] + }, + { + "name": "@vscode/win32-app-container-tokens", + "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 6c3b9720b37..932f6d682aa 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -539,6 +539,40 @@ "isOnlyProductionDependency": true, "license": "MIT", "version": "0.10.0" + }, + { + "name": "@vscode/win32-app-container-tokens", + "component": { + "type": "git", + "git": { + "name": "vscode-win32-app-container-tokens", + "repositoryUrl": "https://github.com/microsoft/vscode-win32-app-container-tokens", + "commitHash": "5b871f95fd9cb8efa8ee9a80600510d5e5339137" + } + }, + "licenseDetail":[ + "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" + ] } ], "version": 1 diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index 29876c8523b..79c46dad831 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/better-cpp-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-cpp-syntax", - "commitHash": "ddcaa65af8a578881e0d38f3c1cf5259a1128ab5" + "commitHash": "924295fc44bde1a00fab60da3a2caca4509adb25" } }, "license": "MIT", - "version": "1.15.17", + "version": "1.15.18", "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." }, { diff --git a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json index c85993386c5..b8a6aa53111 100644 --- a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json +++ b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-cpp-syntax/commit/ddcaa65af8a578881e0d38f3c1cf5259a1128ab5", + "version": "https://github.com/jeff-hykin/better-cpp-syntax/commit/924295fc44bde1a00fab60da3a2caca4509adb25", "name": "C++", "scopeName": "source.cpp.embedded.macro", "patterns": [ @@ -519,7 +519,7 @@ "name": "comment.block.cpp" }, "builtin_storage_type_initilizer": { - "begin": "(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", "end": "\\}|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", "end": "\\)|(?=(?))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "punctuation.definition.function.return-type.cpp" @@ -4240,7 +4240,7 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", @@ -5770,7 +5770,7 @@ "include": "#attributes_context" }, { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "begin": "(?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:new)|(?:\\->\\*)|(?:<<=)|(?:>>=)|(?:<=>)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:\\-\\-)|(?:<<)|(?:>>)|(?:<=)|(?:>=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|(?:\\/=)|(?:%=)|(?:&=)|(?:\\^=)|(?:\\|=)|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=|,))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -7351,7 +7351,7 @@ "include": "source.cpp#vararg_ellipses" }, { - "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -9428,7 +9428,7 @@ "endCaptures": {}, "patterns": [ { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { "0": { @@ -2176,7 +2176,7 @@ ] }, "control_flow_keywords": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", "end": "\\}", "beginCaptures": { "1": { @@ -3363,7 +3363,7 @@ ] }, "destructor_root": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { "0": { @@ -3784,7 +3784,7 @@ "match": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))", "beginCaptures": { "0": { @@ -4519,7 +4519,7 @@ ] }, "function_call": { - "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", "end": "\\)", "beginCaptures": { "1": { @@ -4593,7 +4593,7 @@ ] }, "function_definition": { - "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { "0": { @@ -5156,7 +5156,7 @@ ] }, { - "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "punctuation.definition.function.return-type.cpp" @@ -5404,7 +5404,7 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { @@ -5755,7 +5755,7 @@ ] }, "function_pointer_parameter": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { @@ -6174,19 +6174,16 @@ ] }, "goto_statement": { - "match": "((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", "captures": { "1": { - "name": "keyword.control.goto.cpp" - }, - "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "patterns": [ { "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", @@ -6213,7 +6210,44 @@ } ] }, + "3": { + "name": "keyword.control.goto.cpp" + }, "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "6": { "name": "entity.name.label.call.cpp" } } @@ -6476,7 +6510,7 @@ "name": "storage.type.modifier.virtual.cpp" }, { - "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", @@ -6674,7 +6708,7 @@ ] }, "inline_builtin_storage_type": { - "match": "(?:\\s)*+(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least32_t[^\\w]|uint_least16_t[^\\w]|uint_least64_t[^\\w]|int_least32_t[^\\w]|int_least64_t[^\\w]|uint_fast32_t[^\\w]|uint_fast64_t[^\\w]|uint_least8_t[^\\w]|uint_fast16_t[^\\w]|int_least16_t[^\\w]|int_fast16_t[^\\w]|int_least8_t[^\\w]|uint_fast8_t[^\\w]|int_fast64_t[^\\w]|int_fast32_t[^\\w]|int_fast8_t[^\\w]|suseconds_t[^\\w]|useconds_t[^\\w]|in_addr_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|in_port_t[^\\w]|uintptr_t[^\\w]|blksize_t[^\\w]|uint32_t[^\\w]|uint64_t[^\\w]|u_quad_t[^\\w]|intmax_t[^\\w]|intmax_t[^\\w]|unsigned[^\\w]|blkcnt_t[^\\w]|uint16_t[^\\w]|intptr_t[^\\w]|swblk_t[^\\w]|wchar_t[^\\w]|u_short[^\\w]|qaddr_t[^\\w]|caddr_t[^\\w]|daddr_t[^\\w]|fixpt_t[^\\w]|nlink_t[^\\w]|segsz_t[^\\w]|clock_t[^\\w]|ssize_t[^\\w]|int16_t[^\\w]|int32_t[^\\w]|int64_t[^\\w]|uint8_t[^\\w]|int8_t[^\\w]|mode_t[^\\w]|quad_t[^\\w]|ushort[^\\w]|u_long[^\\w]|u_char[^\\w]|double[^\\w]|signed[^\\w]|time_t[^\\w]|size_t[^\\w]|key_t[^\\w]|div_t[^\\w]|ino_t[^\\w]|uid_t[^\\w]|gid_t[^\\w]|off_t[^\\w]|pid_t[^\\w]|float[^\\w]|dev_t[^\\w]|u_int[^\\w]|short[^\\w]|bool[^\\w]|id_t[^\\w]|uint[^\\w]|long[^\\w]|char[^\\w]|void[^\\w]|auto[^\\w]|id_t[^\\w]|int[^\\w])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", + "match": "(?:((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least16_t[^\\w]|uint_least32_t[^\\w]|uint_least64_t[^\\w]|int_least16_t[^\\w]|int_least32_t[^\\w]|int_least64_t[^\\w]|uint_least8_t[^\\w]|uint_fast16_t[^\\w]|uint_fast32_t[^\\w]|uint_fast64_t[^\\w]|int_least8_t[^\\w]|int_fast16_t[^\\w]|int_fast32_t[^\\w]|int_fast64_t[^\\w]|uint_fast8_t[^\\w]|suseconds_t[^\\w]|int_fast8_t[^\\w]|useconds_t[^\\w]|blksize_t[^\\w]|in_addr_t[^\\w]|in_port_t[^\\w]|uintptr_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|unsigned[^\\w]|u_quad_t[^\\w]|blkcnt_t[^\\w]|uint16_t[^\\w]|uint32_t[^\\w]|uint64_t[^\\w]|intptr_t[^\\w]|intmax_t[^\\w]|intmax_t[^\\w]|wchar_t[^\\w]|u_short[^\\w]|qaddr_t[^\\w]|caddr_t[^\\w]|daddr_t[^\\w]|fixpt_t[^\\w]|nlink_t[^\\w]|segsz_t[^\\w]|swblk_t[^\\w]|clock_t[^\\w]|ssize_t[^\\w]|int16_t[^\\w]|int32_t[^\\w]|int64_t[^\\w]|uint8_t[^\\w]|signed[^\\w]|double[^\\w]|u_char[^\\w]|u_long[^\\w]|ushort[^\\w]|quad_t[^\\w]|mode_t[^\\w]|size_t[^\\w]|time_t[^\\w]|int8_t[^\\w]|short[^\\w]|float[^\\w]|u_int[^\\w]|div_t[^\\w]|dev_t[^\\w]|gid_t[^\\w]|ino_t[^\\w]|key_t[^\\w]|pid_t[^\\w]|off_t[^\\w]|uid_t[^\\w]|auto[^\\w]|void[^\\w]|char[^\\w]|long[^\\w]|bool[^\\w]|uint[^\\w]|id_t[^\\w]|id_t[^\\w]|int[^\\w])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", "captures": { "1": { "patterns": [ @@ -7575,7 +7609,7 @@ ] }, "namespace_alias": { - "match": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "begin": "(?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:new)|(?:\\->\\*)|(?:<<=)|(?:>>=)|(?:<=>)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:\\-\\-)|(?:<<)|(?:>>)|(?:<=)|(?:>=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|(?:\\/=)|(?:%=)|(?:&=)|(?:\\^=)|(?:\\|=)|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=|,))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))", "beginCaptures": { "0": { @@ -10441,7 +10475,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -11479,7 +11513,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -12814,7 +12848,7 @@ ] }, "qualified_type": { - "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", "captures": { "0": { "patterns": [ @@ -13084,7 +13118,7 @@ } }, "scope_resolution": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13106,7 +13140,7 @@ } }, "scope_resolution_function_call": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13128,7 +13162,7 @@ } }, "scope_resolution_function_call_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13166,7 +13200,7 @@ } }, "scope_resolution_function_definition": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13188,7 +13222,7 @@ } }, "scope_resolution_function_definition_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13226,7 +13260,7 @@ } }, "scope_resolution_function_definition_operator_overload": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13248,7 +13282,7 @@ } }, "scope_resolution_function_definition_operator_overload_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13286,7 +13320,7 @@ } }, "scope_resolution_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13324,7 +13358,7 @@ } }, "scope_resolution_namespace_alias": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13346,7 +13380,7 @@ } }, "scope_resolution_namespace_alias_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13384,7 +13418,7 @@ } }, "scope_resolution_namespace_block": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13406,7 +13440,7 @@ } }, "scope_resolution_namespace_block_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13444,7 +13478,7 @@ } }, "scope_resolution_namespace_using": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13466,7 +13500,7 @@ } }, "scope_resolution_namespace_using_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13504,7 +13538,7 @@ } }, "scope_resolution_parameter": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13526,7 +13560,7 @@ } }, "scope_resolution_parameter_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13564,7 +13598,7 @@ } }, "scope_resolution_template_call": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13586,7 +13620,7 @@ } }, "scope_resolution_template_call_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13624,7 +13658,7 @@ } }, "scope_resolution_template_definition": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13646,7 +13680,7 @@ } }, "scope_resolution_template_definition_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13688,7 +13722,7 @@ "name": "punctuation.terminator.statement.cpp" }, "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", "captures": { "1": { "name": "meta.qualified_type.cpp", @@ -16492,7 +16526,7 @@ } }, "type_alias": { - "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", + "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -17582,7 +17616,7 @@ "endCaptures": {}, "patterns": [ { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()", "beginCaptures": { "1": { @@ -18867,7 +18901,7 @@ ] }, "typename": { - "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", + "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -19756,7 +19790,7 @@ } }, "using_namespace": { - "begin": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((? { - if (e.affectsConfiguration('git.postCommitCommand', root) || - e.affectsConfiguration('git.showActionButton', root) - ) { + if (e.affectsConfiguration('git.enableSmartCommit', root) || + e.affectsConfiguration('git.smartCommitChanges', root) || + e.affectsConfiguration('git.suggestSmartCommit', root)) { + this.onDidChangeSmartCommitSettings(); + } + + if (e.affectsConfiguration('git.branchProtection', root) || + e.affectsConfiguration('git.branchProtectionPrompt', root) || + e.affectsConfiguration('git.postCommitCommand', root) || + e.affectsConfiguration('git.showActionButton', root)) { this._onDidChange.fire(); } })); } get button(): SourceControlActionButton | undefined { - if (!this.state.HEAD || !this.state.HEAD.name || !this.state.HEAD.commit) { return undefined; } + if (!this.state.HEAD || !this.state.HEAD.name) { return undefined; } let actionButton: SourceControlActionButton | undefined; - if (this.state.repositoryHasNoChanges) { - if (this.state.HEAD.upstream) { - // Sync Changes - actionButton = this.getSyncChangesActionButton(); - } else { - // Publish Branch - actionButton = this.getPublishBranchActionButton(); - } - } else { - // Commit Changes + + if (this.state.repositoryHasChangesToCommit) { + // Commit Changes (enabled) actionButton = this.getCommitActionButton(); } - return actionButton; + // Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled) + return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton(); } private getCommitActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true }); - if (showActionButton.commit) { - let title: string, tooltip: string; - const postCommitCommand = config.get('postCommitCommand'); + // The button is disabled + if (!showActionButton.commit) { return undefined; } - switch (postCommitCommand) { - case 'push': { - title = localize('scm button commit and push title', "$(arrow-up) Commit & Push"); - tooltip = this.state.isActionRunning ? - localize('scm button committing pushing tooltip', "Committing & Pushing Changes...") : - localize('scm button commit push tooltip', "Commit & Push Changes"); - break; + let title: string, tooltip: string; + const postCommitCommand = config.get('postCommitCommand'); + + // Branch protection + const isBranchProtected = this.repository.isBranchProtected(); + const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; + const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; + const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; + + // Icon + const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; + + // Title, tooltip + switch (postCommitCommand) { + case 'push': { + title = localize('scm button commit and push title', "{0} Commit & Push", icon ?? '$(arrow-up)'); + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch and pushing tooltip', "Committing to New Branch & Pushing Changes...") : + localize('scm button commit to new branch and push tooltip', "Commit to New Branch & Push Changes"); + } else { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing and pushing tooltip', "Committing & Pushing Changes...") : + localize('scm button commit and push tooltip', "Commit & Push Changes"); } - case 'sync': { - title = localize('scm button commit and sync title', "$(sync) Commit & Sync"); - tooltip = this.state.isActionRunning ? - localize('scm button committing synching tooltip', "Committing & Synching Changes...") : - localize('scm button commit sync tooltip', "Commit & Sync Changes"); - break; + break; + } + case 'sync': { + title = localize('scm button commit and sync title', "{0} Commit & Sync", icon ?? '$(sync)'); + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch and synching tooltip', "Committing to New Branch & Synching Changes...") : + localize('scm button commit to new branch and sync tooltip', "Commit to New Branch & Sync Changes"); + } else { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing and synching tooltip', "Committing & Synching Changes...") : + localize('scm button commit and sync tooltip', "Commit & Sync Changes"); } - default: { - title = localize('scm button commit title', "$(check) Commit"); - tooltip = this.state.isActionRunning ? + break; + } + default: { + title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)'); + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch tooltip', "Committing Changes to New Branch...") : + localize('scm button commit to new branch tooltip', "Commit Changes to New Branch"); + } else { + tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); - break; } + break; } - - return { - command: { - command: 'git.commit', - title: title, - tooltip: tooltip, - arguments: [this.repository.sourceControl], - }, - secondaryCommands: [ - [ - { - command: 'git.commit', - title: 'Commit', - arguments: [this.repository.sourceControl, ''], - }, - { - command: 'git.commit', - title: 'Commit & Push', - arguments: [this.repository.sourceControl, 'push'], - }, - { - command: 'git.commit', - title: 'Commit & Sync', - arguments: [this.repository.sourceControl, 'sync'], - }, - ] - ], - enabled: !this.state.isActionRunning - }; } - return undefined; + return { + command: { + command: 'git.commit', + title: title, + tooltip: tooltip, + arguments: [this.repository.sourceControl], + }, + secondaryCommands: [ + [ + { + command: 'git.commit', + title: localize('scm secondary button commit', "Commit"), + arguments: [this.repository.sourceControl, ''], + }, + { + command: 'git.commit', + title: localize('scm secondary button commit and push', "Commit & Push"), + arguments: [this.repository.sourceControl, 'push'], + }, + { + command: 'git.commit', + title: localize('scm secondary button commit and sync', "Commit & Sync"), + arguments: [this.repository.sourceControl, 'sync'], + }, + ] + ], + enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress + }; } private getPublishBranchActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - if (showActionButton.publish) { - return { - command: { - command: 'git.publish', - title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'), - tooltip: this.state.isActionRunning ? - localize('scm button publish branch running', "Publishing Branch...") : - localize('scm button publish branch', "Publish Branch"), - arguments: [this.repository.sourceControl], - }, - enabled: !this.state.isActionRunning - }; - } + // Branch does have an upstream, commit/merge is in progress, or the button is disabled + if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.publish) { return undefined; } - return undefined; + return { + command: { + command: 'git.publish', + title: localize('scm publish branch action button title', "{0} Publish Branch", '$(cloud-upload)'), + tooltip: this.state.isSyncInProgress ? + localize('scm button publish branch running', "Publishing Branch...") : + localize('scm button publish branch', "Publish Branch"), + arguments: [this.repository.sourceControl], + }, + enabled: !this.state.isSyncInProgress + }; } private getSyncChangesActionButton(): SourceControlActionButton | undefined { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); - if (this.state.HEAD?.ahead && showActionButton.sync) { - const rebaseWhenSync = config.get('rebaseWhenSync'); + // Branch does not have an upstream, commit/merge is in progress, or the button is disabled + if (!this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.sync) { return undefined; } - const ahead = `${this.state.HEAD.ahead}$(arrow-up)`; - const behind = this.state.HEAD.behind ? `${this.state.HEAD.behind}$(arrow-down) ` : ''; - const icon = this.state.isActionRunning ? '$(sync~spin)' : '$(sync)'; + const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; + const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)'; - return { - command: { - command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync', - title: `${icon} ${behind} ${ahead}`, - tooltip: this.state.isActionRunning ? - localize('syncing changes', "Synchronizing Changes...") - : this.repository.syncTooltip, - arguments: [this.repository.sourceControl], - }, - description: localize('scm button sync description', "{0} Sync Changes {1}{2}", icon, behind, ahead), - enabled: !this.state.isActionRunning - }; - } + const rebaseWhenSync = config.get('rebaseWhenSync'); - return undefined; + return { + command: { + command: rebaseWhenSync ? 'git.syncRebase' : 'git.sync', + title: `${icon}${behind}${ahead}`, + tooltip: this.state.isSyncInProgress ? + localize('syncing changes', "Synchronizing Changes...") + : this.repository.syncTooltip, + arguments: [this.repository.sourceControl], + }, + description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead), + enabled: !this.state.isSyncInProgress + }; } private onDidChangeOperations(): void { - const isActionRunning = - this.repository.operations.isRunning(Operation.Sync) || - this.repository.operations.isRunning(Operation.Push) || - this.repository.operations.isRunning(Operation.Pull) || + const isCommitInProgress = this.repository.operations.isRunning(Operation.Commit); - this.state = { ...this.state, isActionRunning }; + const isSyncInProgress = + this.repository.operations.isRunning(Operation.Sync) || + this.repository.operations.isRunning(Operation.Push) || + this.repository.operations.isRunning(Operation.Pull); + + this.state = { ...this.state, isCommitInProgress, isSyncInProgress }; + } + + private onDidChangeSmartCommitSettings(): void { + this.state = { + ...this.state, + repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() + }; } private onDidRunGitStatus(): void { this.state = { ...this.state, HEAD: this.repository.HEAD, - repositoryHasNoChanges: - this.repository.indexGroup.resourceStates.length === 0 && - this.repository.mergeGroup.resourceStates.length === 0 && - this.repository.untrackedGroup.resourceStates.length === 0 && - this.repository.workingTreeGroup.resourceStates.length === 0 + isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0, + repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() }; } + private repositoryHasChangesToCommit(): boolean { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const enableSmartCommit = config.get('enableSmartCommit') === true; + const suggestSmartCommit = config.get('suggestSmartCommit') === true; + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all'); + + const resources = [...this.repository.indexGroup.resourceStates]; + + if ( + // Smart commit enabled (all) + (enableSmartCommit && smartCommitChanges === 'all') || + // Smart commit disabled, smart suggestion enabled + (!enableSmartCommit && suggestSmartCommit) + ) { + resources.push(...this.repository.workingTreeGroup.resourceStates); + } + + // Smart commit enabled (tracked only) + if (enableSmartCommit && smartCommitChanges === 'tracked') { + resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED)); + } + + return resources.length !== 0; + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 53d97a60e35..22649e86fc7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -28,6 +28,7 @@ class CheckoutItem implements QuickPickItem { protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } + get refName(): string | undefined { return this.ref.name; } constructor(protected repository: Repository, protected ref: Ref) { } @@ -140,6 +141,7 @@ class HEADItem implements QuickPickItem { get label(): string { return 'HEAD'; } get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); } get alwaysShow(): boolean { return true; } + get refName(): string { return 'HEAD'; } } class AddRemoteItem implements QuickPickItem { @@ -1104,13 +1106,21 @@ export class CommandCenter { } await doc.save(); - await repository.add([uri]); // TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor // uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab + // see https://github.com/microsoft/vscode/issues/153213 const { activeTab } = window.tabGroups.activeTabGroup; + let didCloseTab = false; if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) { - await window.tabGroups.close(activeTab, true); + didCloseTab = await window.tabGroups.close(activeTab, true); + } + + // Only stage if the merge editor has been successfully closed. That means all conflicts have been + // handled or unhandled conflicts are OK by the user. + if (didCloseTab) { + await repository.add([uri]); + await commands.executeCommand('workbench.view.scm'); } } @@ -1615,7 +1625,7 @@ export class CommandCenter { const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); if ((opts.postCommitCommand === undefined && postCommitCommand === 'push') || opts.postCommitCommand === 'push') { - await this._push(repository, { pushType: PushType.Push, silent: true }); + await this._push(repository, { pushType: PushType.Push }); } if ((opts.postCommitCommand === undefined && postCommitCommand === 'sync') || opts.postCommitCommand === 'sync') { await this.sync(repository); @@ -1701,6 +1711,51 @@ export class CommandCenter { await this.commitWithAnyInput(repository, { all: true, amend: true }); } + @command('git.commitMessageAccept') + async commitMessageAccept(arg?: Uri): Promise { + if (!arg) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + @command('git.commitMessageDiscard') + async commitMessageDiscard(arg?: Uri): Promise { + if (!arg) { return; } + + // Clear the contents of the editor + const editors = window.visibleTextEditors + .filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg.toString()); + + if (editors.length !== 1) { return; } + + const commitMsgEditor = editors[0]; + const commitMsgDocument = commitMsgEditor.document; + + const editResult = await commitMsgEditor.edit(builder => { + const firstLine = commitMsgDocument.lineAt(0); + const lastLine = commitMsgDocument.lineAt(commitMsgDocument.lineCount - 1); + + builder.delete(new Range(firstLine.range.start, lastLine.range.end)); + }); + + if (!editResult) { return; } + + // Save the document + const saveResult = await commitMsgDocument.save(); + if (!saveResult) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + private _closeEditorTab(uri: Uri): void { + const tabToClose = window.tabGroups.all.map(g => g.tabs).flat() + .filter(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString()); + + window.tabGroups.close(tabToClose); + } + private async _commitEmpty(repository: Repository, noVerify?: boolean): Promise { const root = Uri.file(repository.root); const config = workspace.getConfiguration('git', root); @@ -1998,7 +2053,9 @@ export class CommandCenter { return; } - target = choice.label; + if (choice.refName) { + target = choice.refName; + } } await repository.branch(branchName, true, target); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d6b9ef01b70..18d66247661 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1785,12 +1785,12 @@ export class Repository { } catch (err) { if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.PushRejected; + } else if (/Permission.*denied/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.PermissionDenied; } else if (/Could not read from remote repository/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; } else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoUpstreamBranch; - } else if (/Permission.*denied/.test(err.stderr || '')) { - err.gitErrorCode = GitErrorCodes.PermissionDenied; } throw err; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index dd5d72e890c..eb0de19bcf6 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -616,7 +616,7 @@ class ResourceCommandResolver { if (!resource.leftUri) { const bothModified = resource.type === Status.BOTH_MODIFIED; - if (resource.rightUri && bothModified && workspace.getConfiguration('git').get('experimental.mergeEditor', false)) { + if (resource.rightUri && bothModified && workspace.getConfiguration('git').get('mergeEditor', false)) { return { command: '_git.openMergeEditor', title: localize('open.merge', "Open Merge"), @@ -935,7 +935,7 @@ export class Repository implements Disposable { updateIndexGroupVisibility(); workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('git.experimental.mergeEditor')) { + if (e.affectsConfiguration('git.mergeEditor')) { this.mergeGroup.resourceStates = this.mergeGroup.resourceStates.map(r => r.clone()); } }, undefined, this.disposables); diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index ef4044fdd1c..a5d6607b1d8 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -7,6 +7,42 @@ resolved "https://registry.yarnpkg.com/@joaomoreno/unique-names-generator/-/unique-names-generator-5.0.0.tgz#a67fe66e3d825c929fc97abfdf17fd80a72beab0" integrity sha512-3kP6z7aoGEoM3tvhTBZioYa1QFkovOU8uxAlVclnZlXivwF/WTE5EcOzvoDdM+jtjJyWvCMDR1Q4RBjDqupD3A== +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/byline@4.2.31": version "4.2.31" resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.31.tgz#0e61fcb9c03e047d21c4496554c7116297ab60cd" @@ -46,10 +82,13 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" "@vscode/iconv-lite-umd@0.7.0": version "0.7.0" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 78094e3c5f5..a130c199928 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -48,7 +48,7 @@ } } }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", "scripts": { @@ -61,7 +61,7 @@ "dependencies": { "node-fetch": "2.6.7", "uuid": "8.1.0", - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "vscode-nls": "^5.0.0", "vscode-tas-client": "^0.1.47" }, diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index a390930318e..118ed320b0f 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -25,10 +61,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" asynckit@^0.4.0: version "0.4.0" diff --git a/extensions/github/package.json b/extensions/github/package.json index af7b544f596..fb33fbaf3a5 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -25,11 +25,22 @@ "supported": true } }, + "enabledApiProposals": [ + "contribShareMenu" + ], "contributes": { "commands": [ { "command": "github.publish", "title": "Publish to GitHub" + }, + { + "command": "github.copyVscodeDevLink", + "title": "Copy vscode.dev Link" + }, + { + "command": "github.copyVscodeDevLinkFile", + "title": "Copy vscode.dev Link" } ], "menus": { @@ -37,6 +48,26 @@ { "command": "github.publish", "when": "git-base.gitEnabled" + }, + { + "command": "github.copyVscodeDevLink", + "when": "false" + }, + { + "command": "github.copyVscodeDevLinkFile", + "when": "false" + } + ], + "file/share": [ + { + "command": "github.copyVscodeDevLinkFile", + "when": "github.hasGitHubRepo" + } + ], + "editor/context/share": [ + { + "command": "github.copyVscodeDevLink", + "when": "github.hasGitHubRepo && resourceScheme != untitled" } ] }, diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index b43271a87a0..4e9bdab9dfa 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -6,6 +6,8 @@ "welcome.publishFolder": { "message": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ + "{Locked='$(github)'}", + "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" @@ -14,6 +16,8 @@ "welcome.publishWorkspaceFolder": { "message": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ + "{Locked='$(github)'}", + "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 42ed8198287..8c68f36bfc6 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -7,6 +7,18 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; import { publishRepository } from './publish'; import { DisposableStore } from './util'; +import { getPermalink } from './links'; + +async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { + try { + const permalink = getPermalink(gitAPI, useSelection, 'https://vscode.dev/github'); + if (permalink) { + return vscode.env.clipboard.writeText(permalink); + } + } catch (err) { + vscode.window.showErrorMessage(err.message); + } +} export function registerCommands(gitAPI: GitAPI): vscode.Disposable { const disposables = new DisposableStore(); @@ -19,5 +31,13 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable { } })); + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => { + return copyVscodeDevLink(gitAPI, true); + })); + + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => { + return copyVscodeDevLink(gitAPI, false); + })); + return disposables; } diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index 2fbe1d597fd..a3a84b033dd 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -5,10 +5,10 @@ import { commands, Disposable, ExtensionContext, extensions } from 'vscode'; import { GithubRemoteSourceProvider } from './remoteSourceProvider'; -import { GitExtension } from './typings/git'; +import { API, GitExtension } from './typings/git'; import { registerCommands } from './commands'; import { GithubCredentialProviderManager } from './credentialProvider'; -import { DisposableStore } from './util'; +import { DisposableStore, repositoryHasGitHubRemote } from './util'; import { GithubPushErrorHandler } from './pushErrorHandler'; import { GitBaseExtension } from './typings/git-base'; import { GithubRemoteSourcePublisher } from './remoteSourcePublisher'; @@ -48,6 +48,21 @@ function initializeGitBaseExtension(): Disposable { return disposables; } +function setGitHubContext(gitAPI: API, disposables: DisposableStore) { + if (gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) { + commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + } else { + const openRepoDisposable = gitAPI.onDidOpenRepository(async e => { + await e.status(); + if (repositoryHasGitHubRemote(e)) { + commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + openRepoDisposable.dispose(); + } + }); + disposables.add(openRepoDisposable); + } +} + function initializeGitExtension(): Disposable { const disposables = new DisposableStore(); @@ -64,6 +79,7 @@ function initializeGitExtension(): Disposable { disposables.add(new GithubCredentialProviderManager(gitAPI)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler())); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); + setGitHubContext(gitAPI, disposables); commands.executeCommand('setContext', 'git-base.gitEnabled', true); } else { diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts new file mode 100644 index 00000000000..22ede57ccbd --- /dev/null +++ b/extensions/github/src/links.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { API as GitAPI, Repository } from './typings/git'; +import { getRepositoryFromUrl } from './util'; + +export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean { + return file.path.toLowerCase() === repository.rootUri.path.toLowerCase() || + (file.path.toLowerCase().startsWith(repository.rootUri.path.toLowerCase()) && + file.path.substring(repository.rootUri.path.length).startsWith('/')); +} + +export function getRepositoryForFile(gitAPI: GitAPI, file: vscode.Uri): Repository | undefined { + for (const repository of gitAPI.repositories) { + if (isFileInRepo(repository, file)) { + return repository; + } + } + return undefined; +} + +enum LinkType { + File = 1, + Notebook = 2 +} + +interface IFilePosition { + type: LinkType.File; + uri: vscode.Uri; + range: vscode.Range | undefined; +} + +interface INotebookPosition { + type: LinkType.Notebook; + uri: vscode.Uri; + cellIndex: number; + range: vscode.Range | undefined; +} + +function getFileAndPosition(): IFilePosition | INotebookPosition | undefined { + let uri: vscode.Uri | undefined; + let range: vscode.Range | undefined; + if (vscode.window.activeTextEditor) { + uri = vscode.window.activeTextEditor.document.uri; + + if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) { + // if the active editor is a notebook editor and the focus is inside any a cell text editor + // generate deep link for text selection for the notebook cell. + const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment); + const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start; + const range = cell !== undefined ? vscode.window.activeTextEditor.selection : undefined; + return { type: LinkType.Notebook, uri, cellIndex, range }; + } else { + // the active editor is a text editor + range = vscode.window.activeTextEditor.selection; + return { type: LinkType.File, uri, range }; + } + } + + if (vscode.window.activeNotebookEditor) { + // if the active editor is a notebook editor but the focus is not inside any cell text editor, generate deep link for the cell selection in the notebook document. + return { type: LinkType.Notebook, uri: vscode.window.activeNotebookEditor.notebook.uri, cellIndex: vscode.window.activeNotebookEditor.selection.start, range: undefined }; + } + + return undefined; +} + +function rangeString(range: vscode.Range | undefined) { + if (!range) { + return ''; + } + let hash = `#L${range.start.line + 1}`; + if (range.start.line !== range.end.line) { + hash += `-L${range.end.line + 1}`; + } + return hash; +} + +export function notebookCellRangeString(index: number | undefined, range: vscode.Range | undefined) { + if (index === undefined) { + return ''; + } + + if (!range) { + return `#C${index + 1}`; + } + + let hash = `#C${index + 1}:L${range.start.line + 1}`; + if (range.start.line !== range.end.line) { + hash += `-L${range.end.line + 1}`; + } + return hash; +} + +export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined { + hostPrefix = hostPrefix ?? 'https://github.com'; + const fileAndPosition = getFileAndPosition(); + if (!fileAndPosition) { + return; + } + const uri = fileAndPosition.uri; + + // Use the first repo if we cannot determine a repo from the uri. + const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0]; + if (!gitRepo) { + return; + } + let repo: { owner: string; repo: string } | undefined; + gitRepo.state.remotes.find(remote => { + if (remote.fetchUrl) { + const foundRepo = getRepositoryFromUrl(remote.fetchUrl); + if (foundRepo && (remote.name === gitRepo.state.HEAD?.upstream?.remote)) { + repo = foundRepo; + return; + } else if (foundRepo && !repo) { + repo = foundRepo; + } + } + return; + }); + if (!repo) { + return; + } + + const commitHash = gitRepo.state.HEAD?.commit; + const fileSegments = fileAndPosition.type === LinkType.File + ? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '') + : (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : ''); + + return `${hostPrefix}/${repo.owner}/${repo.repo}/blob/${commitHash + }${fileSegments}`; +} diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index e365a7172b7..e8eeb851549 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -7,17 +7,7 @@ import { workspace } from 'vscode'; import { RemoteSourceProvider, RemoteSource } from './typings/git-base'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; - -function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined { - const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url) - || /^git@github\.com:([^/]+)\/([^/]+)\.git/i.exec(url); - return match ? { owner: match[1], repo: match[2] } : undefined; -} - -function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined { - const match = /^([^/]+)\/([^/]+)$/i.exec(query); - return match ? { owner: match[1], repo: match[2] } : undefined; -} +import { getRepositoryFromQuery, getRepositoryFromUrl } from './util'; function asRemoteSource(raw: any): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); diff --git a/extensions/github/src/util.ts b/extensions/github/src/util.ts index c7d23a82301..3d8bf4a40be 100644 --- a/extensions/github/src/util.ts +++ b/extensions/github/src/util.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { Repository } from './typings/git'; export class DisposableStore { @@ -21,3 +22,18 @@ export class DisposableStore { this.disposables.clear(); } } + +export function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined { + const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/i.exec(url) + || /^git@github\.com:([^/]+)\/([^/]+?)(\.git)?$/i.exec(url); + return match ? { owner: match[1], repo: match[2] } : undefined; +} + +export function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined { + const match = /^([^/]+)\/([^/]+)$/i.exec(query); + return match ? { owner: match[1], repo: match[2] } : undefined; +} + +export function repositoryHasGitHubRemote(repository: Repository) { + return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined); +} diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 51d53e8984a..b3731cd0acd 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -5,7 +5,7 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "0.10.x" }, diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 10a70dbe99a..0c199571dd7 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -93,13 +93,16 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { }; } +const ignoredErrors = [ + 1108, /* A_return_statement_can_only_be_used_within_a_function_body_1108 */ + 2792, /* Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option */ +]; export function getJavaScriptMode(documentRegions: LanguageModelCache, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode { const jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId)); const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS); const globalSettings: Settings = {}; - return { getId() { return languageId; @@ -110,7 +113,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache d.code !== 1108).map((diag: ts.Diagnostic): Diagnostic => { + return syntaxDiagnostics.concat(semanticDiagnostics).filter(d => !ignoredErrors.includes(d.code)).map((diag: ts.Diagnostic): Diagnostic => { return { range: convertRange(jsDocument, diag), severity: DiagnosticSeverity.Error, diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index b6ff3579894..1ad296a9905 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -10,7 +10,7 @@ "publisher": "vscode", "icon": "icon.png", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.39.0" }, @@ -79,7 +79,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "vscode-nls": "^5.0.0" }, "repository": { diff --git a/extensions/image-preview/yarn.lock b/extensions/image-preview/yarn.lock index 65af1b8b351..da44f29eaa4 100644 --- a/extensions/image-preview/yarn.lock +++ b/extensions/image-preview/yarn.lock @@ -2,10 +2,49 @@ # yarn lockfile v1 -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 60388c911af..b29d3137315 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -5,7 +5,7 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "0.10.x" }, @@ -147,7 +147,7 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "request-light": "^0.5.8", "vscode-languageclient": "^8.0.2-next.4", "vscode-nls": "^5.0.1" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 7cdc2d2ec2d..c7ee46da28d 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,15 +2,54 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" balanced-match@^1.0.0: version "1.0.0" diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index cb6266c338a..c41b84710d4 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "8f2c528619535333a2ff12e321fb6f6efcba38e4" + "commitHash": "7b08daed22963695616ef432c9d027427da5f450" } }, "license": "MIT", diff --git a/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/extensions/latex/syntaxes/LaTeX.tmLanguage.json index e2061e78fa2..de27e1b1158 100644 --- a/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/8f2c528619535333a2ff12e321fb6f6efcba38e4", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/7b08daed22963695616ef432c9d027427da5f450", "name": "LaTeX", "scopeName": "text.tex.latex", "patterns": [ @@ -815,7 +815,7 @@ "name": "meta.function.end-document.latex" }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:array|equation|(?:IEEE)?eqnarray|multline|align|aligned|alignat|alignedat|flalign|flaligned|flalignat|split|gather|gathered|cases|(?:display)?math|[a-zA-Z]*matrix|(?:(?:arg)?(?:mini|maxi)))(?:\\*|!)?)(\\})(\\s*\\n)?", + "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:array|equation|(?:IEEE)?eqnarray|multline|align|aligned|alignat|alignedat|flalign|flaligned|flalignat|split|gather|gathered|cases|(?:display)?math|[a-zA-Z]*matrix|[pbBvV]?NiceMatrix|[pbBvV]?NiceArray|(?:(?:arg)?(?:mini|maxi)))(?:\\*|!)?)(\\})(\\s*\\n)?", "captures": { "1": { "name": "support.function.be.latex" diff --git a/extensions/log/cgmanifest.json b/extensions/log/cgmanifest.json index a34da8b5999..7d7c9466d70 100644 --- a/extensions/log/cgmanifest.json +++ b/extensions/log/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "vscode-logfile-highlighter", "repositoryUrl": "https://github.com/emilast/vscode-logfile-highlighter", - "commitHash": "19807c6a80d29b03ad69e02ffe39e5869a9ce107" + "commitHash": "8acba9307254d1887ac770057767698c82d926c6" } }, "license": "MIT", - "version": "2.11.0" + "version": "2.15.0" } ], "version": 1 diff --git a/extensions/log/syntaxes/log.tmLanguage.json b/extensions/log/syntaxes/log.tmLanguage.json index 1af8a49cfff..2bda9c9a1f2 100644 --- a/extensions/log/syntaxes/log.tmLanguage.json +++ b/extensions/log/syntaxes/log.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/emilast/vscode-logfile-highlighter/commit/19807c6a80d29b03ad69e02ffe39e5869a9ce107", + "version": "https://github.com/emilast/vscode-logfile-highlighter/commit/8acba9307254d1887ac770057767698c82d926c6", "name": "Log file", "scopeName": "text.log", "patterns": [ @@ -96,6 +96,10 @@ "match": "\\b([0-9]+|true|false|null)\\b", "name": "constant.language log.constant" }, + { + "match": "\\b(0x[a-fA-F0-9]+)\\b", + "name": "constant.language log.constant" + }, { "match": "\"[^\"]*\"", "name": "string log.string" diff --git a/extensions/markdown-language-features/extension-browser.webpack.config.js b/extensions/markdown-language-features/extension-browser.webpack.config.js index 1b52e196f91..bad27aab02e 100644 --- a/extensions/markdown-language-features/extension-browser.webpack.config.js +++ b/extensions/markdown-language-features/extension-browser.webpack.config.js @@ -12,7 +12,7 @@ const withBrowserDefaults = require('../shared.webpack.config').browser; module.exports = withBrowserDefaults({ context: __dirname, entry: { - extension: './src/extension.ts' + extension: './src/extension.browser.ts' } }, { configFile: 'tsconfig.browser.json' diff --git a/extensions/markdown-language-features/extension.webpack.config.js b/extensions/markdown-language-features/extension.webpack.config.js index de88398eca0..756d6735a60 100644 --- a/extensions/markdown-language-features/extension.webpack.config.js +++ b/extensions/markdown-language-features/extension.webpack.config.js @@ -15,6 +15,6 @@ module.exports = withDefaults({ mainFields: ['module', 'main'] }, entry: { - extension: './src/extension.ts', + extension: './src/extension.node.ts', } }); diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index eb4234c0cf0..ec4b4d19e62 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -6,11 +6,11 @@ "icon": "icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.20.0" }, - "main": "./out/extension", + "main": "./out/extension.node", "browser": "./dist/browser/extension", "categories": [ "Programming Languages" @@ -47,7 +47,7 @@ "contributes": { "notebookRenderer": [ { - "id": "markdownItRenderer", + "id": "vscode.markdown-it-renderer", "displayName": "Markdown it renderer", "entrypoint": "./notebook-out/index.js", "mimeTypes": [ @@ -534,8 +534,8 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", + "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features-server && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", + "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService watch-extension:markdown-language-features-server", "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-notebook": "node ./esbuild-notebook", @@ -544,13 +544,14 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "dompurify": "^2.3.3", "highlight.js": "^11.4.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.1", "morphdom": "^2.6.1", "picomatch": "^2.3.1", + "vscode-languageclient": "^8.0.1", "vscode-languageserver-textdocument": "^1.0.4", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.3" diff --git a/extensions/markdown-language-features/server/.vscode/launch.json b/extensions/markdown-language-features/server/.vscode/launch.json new file mode 100644 index 00000000000..5753befac8b --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + "name": "Attach", + "type": "node", + "request": "attach", + "port": 6044, + "protocol": "inspector", + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/out/**/*.js"] + }, + { + "name": "Unit Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/../../../node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "args": [ + "--timeout", + "999999", + "--colors" + ], + "cwd": "${workspaceFolder}", + "runtimeExecutable": null, + "runtimeArgs": [], + "env": {}, + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/out/**/*.js"] + } + ] +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/.vscode/tasks.json b/extensions/markdown-language-features/server/.vscode/tasks.json new file mode 100644 index 00000000000..6a159d6a5fa --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/tasks.json @@ -0,0 +1,9 @@ +{ + "version": "0.1.0", + "command": "npm", + "isShellCommand": true, + "showOutput": "silent", + "args": ["run", "watch"], + "isWatching": true, + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/extension-browser.webpack.config.js b/extensions/markdown-language-features/server/extension-browser.webpack.config.js new file mode 100644 index 00000000000..b36d36f936d --- /dev/null +++ b/extensions/markdown-language-features/server/extension-browser.webpack.config.js @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../../shared.webpack.config').browser; +const path = require('path'); + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/browser/main.ts', + }, + output: { + filename: 'main.js', + path: path.join(__dirname, 'dist', 'browser'), + libraryTarget: 'var', + library: 'serverExportVar' + } +}); diff --git a/extensions/markdown-language-features/server/extension.webpack.config.js b/extensions/markdown-language-features/server/extension.webpack.config.js new file mode 100644 index 00000000000..a1917b54dc6 --- /dev/null +++ b/extensions/markdown-language-features/server/extension.webpack.config.js @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../../shared.webpack.config'); +const path = require('path'); + +module.exports = withDefaults({ + context: path.join(__dirname), + entry: { + extension: './src/node/main.ts', + }, + output: { + filename: 'main.js', + path: path.join(__dirname, 'dist', 'node'), + } +}); diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json new file mode 100644 index 00000000000..2ca66e39347 --- /dev/null +++ b/extensions/markdown-language-features/server/package.json @@ -0,0 +1,27 @@ +{ + "name": "vscode-markdown-languageserver", + "description": "Markdown language server", + "version": "1.0.0", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "main": "./out/node/main", + "browser": "./dist/browser/main", + "dependencies": { + "vscode-languageserver": "^8.0.2-next.4", + "vscode-uri": "^3.0.3", + "vscode-languageserver-textdocument": "^1.0.5", + "vscode-languageserver-types": "^3.17.1", + "vscode-markdown-languageservice": "mjbvz/vscode-markdown-languageservice" + }, + "devDependencies": { + "@types/node": "16.x" + }, + "scripts": { + "postinstall": "cd node_modules/vscode-markdown-languageservice && yarn run compile-ext", + "compile": "gulp compile-extension:markdown-language-features-server", + "watch": "gulp watch-extension:markdown-language-features-server" + } +} diff --git a/extensions/markdown-language-features/server/src/browser/main.ts b/extensions/markdown-language-features/server/src/browser/main.ts new file mode 100644 index 00000000000..cc5e12e9c19 --- /dev/null +++ b/extensions/markdown-language-features/server/src/browser/main.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser'; +import { startServer } from '../server'; + +declare let self: any; + +const messageReader = new BrowserMessageReader(self); +const messageWriter = new BrowserMessageWriter(self); + +const connection = createConnection(messageReader, messageWriter); + +startServer(connection); diff --git a/extensions/markdown-language-features/server/src/logging.ts b/extensions/markdown-language-features/server/src/logging.ts new file mode 100644 index 00000000000..2a026caf580 --- /dev/null +++ b/extensions/markdown-language-features/server/src/logging.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogger } from 'vscode-markdown-languageservice'; + +export class LogFunctionLogger implements ILogger { + + private static now(): string { + const now = new Date(); + return String(now.getUTCHours()).padStart(2, '0') + + ':' + String(now.getMinutes()).padStart(2, '0') + + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0'); + } + + private static data2String(data: any): string { + if (data instanceof Error) { + if (typeof data.stack === 'string') { + return data.stack; + } + return data.message; + } + if (typeof data === 'string') { + return data; + } + return JSON.stringify(data, undefined, 2); + } + + constructor( + private readonly _logFn: typeof console.log + ) { } + + public verbose(title: string, message: string, data?: any): void { + this.appendLine(`[Verbose ${LogFunctionLogger.now()}] ${title}: ${message}`); + if (data) { + this.appendLine(LogFunctionLogger.data2String(data)); + } + } + + private appendLine(value: string): void { + this._logFn(value); + } +} + +export const consoleLogger = new LogFunctionLogger(console.log); diff --git a/extensions/markdown-language-features/server/src/node/main.ts b/extensions/markdown-language-features/server/src/node/main.ts new file mode 100644 index 00000000000..609948b803d --- /dev/null +++ b/extensions/markdown-language-features/server/src/node/main.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, createConnection } from 'vscode-languageserver/node'; +import { startServer } from '../server'; + +// Create a connection for the server. +const connection: Connection = createConnection(); + +console.log = connection.console.log.bind(connection.console); +console.error = connection.console.error.bind(connection.console); + +process.on('unhandledRejection', (e: any) => { + connection.console.error(`Unhandled exception ${e}`); +}); + +startServer(connection); diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts new file mode 100644 index 00000000000..8bc1d4b9271 --- /dev/null +++ b/extensions/markdown-language-features/server/src/server.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, Emitter, Event, InitializeParams, InitializeResult, RequestType, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import * as lsp from 'vscode-languageserver-types'; +import * as md from 'vscode-markdown-languageservice'; +import { URI } from 'vscode-uri'; +import { LogFunctionLogger } from './logging'; + + +const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse'); + +class TextDocumentToITextDocumentAdapter implements md.ITextDocument { + public readonly uri: md.IUri; + + public get version(): number { return this._doc.version; } + + public get lineCount(): number { return this._doc.lineCount; } + + constructor( + private readonly _doc: TextDocument, + ) { + this.uri = URI.parse(this._doc.uri); + } + + getText(range?: md.IRange | undefined): string { + return this._doc.getText(range); + } + + positionAt(offset: number): md.IPosition { + return this._doc.positionAt(offset); + } +} + +export function startServer(connection: Connection) { + const documents = new TextDocuments(TextDocument); + documents.listen(connection); + + connection.onInitialize((_params: InitializeParams): InitializeResult => { + return { + capabilities: { + documentSymbolProvider: true, + foldingRangeProvider: true, + selectionRangeProvider: true, + } + }; + }); + + + const parser = new class implements md.IMdParser { + slugifier = md.githubSlugifier; + + async tokenize(document: md.ITextDocument): Promise { + return await connection.sendRequest(parseRequestType, { uri: document.uri.toString() }); + } + }; + + const workspace = new class implements md.IMdWorkspace { + + private readonly _onDidChangeMarkdownDocument = new Emitter(); + onDidChangeMarkdownDocument: Event = this._onDidChangeMarkdownDocument.event; + + private readonly _onDidCreateMarkdownDocument = new Emitter(); + onDidCreateMarkdownDocument: Event = this._onDidCreateMarkdownDocument.event; + + private readonly _onDidDeleteMarkdownDocument = new Emitter(); + onDidDeleteMarkdownDocument: Event = this._onDidDeleteMarkdownDocument.event; + + async getAllMarkdownDocuments(): Promise> { + return documents.all().map(doc => new TextDocumentToITextDocumentAdapter(doc)); + } + hasMarkdownDocument(resource: md.IUri): boolean { + return !!documents.get(resource.toString()); + } + async getOrLoadMarkdownDocument(_resource: md.IUri): Promise { + return undefined; + } + async pathExists(_resource: md.IUri): Promise { + return false; + } + async readDirectory(_resource: md.IUri): Promise<[string, { isDir: boolean }][]> { + return []; + } + }; + + const logger = new LogFunctionLogger(connection.console.log.bind(connection.console)); + const provider = md.createLanguageService(workspace, parser, logger); + + connection.onDocumentSymbol(async (params, token): Promise => { + try { + const document = documents.get(params.textDocument.uri); + if (document) { + return await provider.provideDocumentSymbols(new TextDocumentToITextDocumentAdapter(document), token); + } + } catch (e) { + console.error(e.stack); + } + return []; + }); + + connection.onFoldingRanges(async (params, token): Promise => { + try { + const document = documents.get(params.textDocument.uri); + if (document) { + return await provider.provideFoldingRanges(new TextDocumentToITextDocumentAdapter(document), token); + } + } catch (e) { + console.error(e.stack); + } + return []; + }); + + connection.onSelectionRanges(async (params, token): Promise => { + try { + const document = documents.get(params.textDocument.uri); + if (document) { + return await provider.provideSelectionRanges(new TextDocumentToITextDocumentAdapter(document), params.positions, token); + } + } catch (e) { + console.error(e.stack); + } + return []; + }); + + connection.listen(); +} diff --git a/extensions/markdown-language-features/server/tsconfig.json b/extensions/markdown-language-features/server/tsconfig.json new file mode 100644 index 00000000000..8b4aedde27d --- /dev/null +++ b/extensions/markdown-language-features/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock new file mode 100644 index 00000000000..d0bce38f189 --- /dev/null +++ b/extensions/markdown-language-features/server/yarn.lock @@ -0,0 +1,55 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@16.x": + version "16.11.43" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.43.tgz#555e5a743f76b6b897d47f945305b618525ddbe6" + integrity sha512-GqWykok+3uocgfAJM8imbozrqLnPyTrpFlrryURQlw1EesPUCx5XxTiucWDSFF9/NUEXDuD4bnvHm8xfVGWTpQ== + +vscode-jsonrpc@8.0.2-next.1: + version "8.0.2-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" + integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== + +vscode-languageserver-protocol@3.17.2-next.6: + version "3.17.2-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" + integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== + dependencies: + vscode-jsonrpc "8.0.2-next.1" + vscode-languageserver-types "3.17.2-next.2" + +vscode-languageserver-textdocument@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + +vscode-languageserver-types@3.17.2-next.2: + version "3.17.2-next.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" + integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== + +vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== + +vscode-languageserver@^8.0.2-next.4: + version "8.0.2-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5" + integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A== + dependencies: + vscode-languageserver-protocol "3.17.2-next.6" + +vscode-markdown-languageservice@mjbvz/vscode-markdown-languageservice: + version "0.0.0-alpha.1" + resolved "https://codeload.github.com/mjbvz/vscode-markdown-languageservice/tar.gz/e1a0e00bf6a99cc543da64964cc0995537647d15" + dependencies: + vscode-languageserver-types "^3.17.1" + vscode-uri "^3.0.3" + +vscode-uri@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" + integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== diff --git a/extensions/markdown-language-features/src/client.ts b/extensions/markdown-language-features/src/client.ts new file mode 100644 index 00000000000..0bf5588ec38 --- /dev/null +++ b/extensions/markdown-language-features/src/client.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Token = require('markdown-it/lib/token'); +import * as vscode from 'vscode'; +import { BaseLanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient'; +import * as nls from 'vscode-nls'; +import { IMdParser } from './markdownEngine'; +import { IMdWorkspace } from './workspace'; + +const localize = nls.loadMessageBundle(); + +const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse'); + +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; + + +export async function startClient(factory: LanguageClientConstructor, workspace: IMdWorkspace, parser: IMdParser): Promise { + + const documentSelector = ['markdown']; + + const clientOptions: LanguageClientOptions = { + documentSelector, + synchronize: { + configurationSection: ['markdown'] + }, + initializationOptions: {} + }; + + const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions); + + client.registerProposedFeatures(); + + client.onRequest(parseRequestType, async (e) => { + const uri = vscode.Uri.parse(e.uri); + const doc = await workspace.getOrLoadMarkdownDocument(uri); + if (doc) { + return parser.tokenize(doc); + } else { + return []; + } + }); + + await client.start(); + + return client; +} diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts index 8db059337d9..1b061c9c4e4 100644 --- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/commands/openDocumentLink.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { Command } from '../commandManager'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { openDocumentLink } from '../util/openDocumentLink'; +import { Schemes } from '../util/schemes'; type UriComponents = { readonly scheme?: string; @@ -59,7 +60,7 @@ export class OpenDocumentLinkCommand implements Command { } function reviveUri(parts: any) { - if (parts.scheme === 'file') { + if (parts.scheme === Schemes.file) { return vscode.Uri.file(parts.path); } return vscode.Uri.parse('').with(parts); diff --git a/extensions/markdown-language-features/src/extension.browser.ts b/extensions/markdown-language-features/src/extension.browser.ts new file mode 100644 index 00000000000..717d1c5ccc9 --- /dev/null +++ b/extensions/markdown-language-features/src/extension.browser.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser'; +import { startClient } from './client'; +import { activateShared } from './extension.shared'; +import { VsCodeOutputLogger } from './logging'; +import { IMdParser, MarkdownItEngine } from './markdownEngine'; +import { getMarkdownExtensionContributions } from './markdownExtensions'; +import { githubSlugifier } from './slugify'; +import { IMdWorkspace, VsCodeMdWorkspace } from './workspace'; + +export function activate(context: vscode.ExtensionContext) { + const contributions = getMarkdownExtensionContributions(context); + context.subscriptions.push(contributions); + + const logger = new VsCodeOutputLogger(); + context.subscriptions.push(logger); + + const engine = new MarkdownItEngine(contributions, githubSlugifier, logger); + + const workspace = new VsCodeMdWorkspace(); + context.subscriptions.push(workspace); + + activateShared(context, workspace, engine, logger, contributions); + startServer(context, workspace, engine); +} + +async function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise { + const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/main.js'); + const worker = new Worker(serverMain.toString()); + + await startClient((id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, clientOptions, worker); + }, workspace, parser); +} diff --git a/extensions/markdown-language-features/src/extension.node.ts b/extensions/markdown-language-features/src/extension.node.ts new file mode 100644 index 00000000000..45dba90d3ca --- /dev/null +++ b/extensions/markdown-language-features/src/extension.node.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node'; +import { startClient } from './client'; +import { activateShared } from './extension.shared'; +import { VsCodeOutputLogger } from './logging'; +import { IMdParser, MarkdownItEngine } from './markdownEngine'; +import { getMarkdownExtensionContributions } from './markdownExtensions'; +import { githubSlugifier } from './slugify'; +import { IMdWorkspace, VsCodeMdWorkspace } from './workspace'; + +export function activate(context: vscode.ExtensionContext) { + const contributions = getMarkdownExtensionContributions(context); + context.subscriptions.push(contributions); + + const logger = new VsCodeOutputLogger(); + context.subscriptions.push(logger); + + const engine = new MarkdownItEngine(contributions, githubSlugifier, logger); + + const workspace = new VsCodeMdWorkspace(); + context.subscriptions.push(workspace); + + activateShared(context, workspace, engine, logger, contributions); + startServer(context, workspace, engine); +} + +async function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise { + const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || ''; + + const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/main`; + const serverModule = context.asAbsolutePath(serverMain); + + // The debug options for the server + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; + + // If the extension is launch in debug mode the debug server options are use + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + }; + await startClient((id, name, clientOptions) => { + return new LanguageClient(id, name, serverOptions, clientOptions); + }, workspace, parser); +} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.shared.ts similarity index 83% rename from extensions/markdown-language-features/src/extension.ts rename to extensions/markdown-language-features/src/extension.shared.ts index 32eef08f82e..2a850191004 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -10,45 +10,39 @@ import { registerPasteSupport } from './languageFeatures/copyPaste'; import { registerDefinitionSupport } from './languageFeatures/definitions'; import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; import { MdLinkProvider, registerDocumentLinkSupport } from './languageFeatures/documentLinks'; -import { MdDocumentSymbolProvider, registerDocumentSymbolSupport } from './languageFeatures/documentSymbols'; +import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbols'; import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor'; import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; -import { registerFoldingSupport } from './languageFeatures/folding'; import { registerPathCompletionSupport } from './languageFeatures/pathCompletions'; import { MdReferencesProvider, registerReferencesSupport } from './languageFeatures/references'; import { registerRenameSupport } from './languageFeatures/rename'; -import { registerSmartSelectSupport } from './languageFeatures/smartSelect'; import { registerWorkspaceSymbolSupport } from './languageFeatures/workspaceSymbols'; -import { ILogger, VsCodeOutputLogger } from './logging'; +import { ILogger } from './logging'; import { IMdParser, MarkdownItEngine, MdParsingProvider } from './markdownEngine'; -import { getMarkdownExtensionContributions } from './markdownExtensions'; +import { MarkdownContributionProvider } from './markdownExtensions'; import { MdDocumentRenderer } from './preview/documentRenderer'; import { MarkdownPreviewManager } from './preview/previewManager'; import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security'; -import { githubSlugifier } from './slugify'; import { MdTableOfContentsProvider } from './tableOfContents'; import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; -import { IMdWorkspace, VsCodeMdWorkspace } from './workspace'; +import { IMdWorkspace } from './workspace'; - -export function activate(context: vscode.ExtensionContext) { +export function activateShared( + context: vscode.ExtensionContext, + workspace: IMdWorkspace, + engine: MarkdownItEngine, + logger: ILogger, + contributions: MarkdownContributionProvider, +) { const telemetryReporter = loadDefaultTelemetryReporter(); context.subscriptions.push(telemetryReporter); - const contributions = getMarkdownExtensionContributions(context); - context.subscriptions.push(contributions); - - const logger = new VsCodeOutputLogger(); - context.subscriptions.push(logger); - const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); const commandManager = new CommandManager(); - const engine = new MarkdownItEngine(contributions, githubSlugifier, logger); - const workspace = new VsCodeMdWorkspace(); const parser = new MdParsingProvider(engine, workspace); const tocProvider = new MdTableOfContentsProvider(parser, workspace, logger); - context.subscriptions.push(workspace, parser, tocProvider); + context.subscriptions.push(parser, tocProvider); const contentProvider = new MdDocumentRenderer(engine, context, cspArbiter, contributions, logger); const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider); @@ -83,15 +77,12 @@ function registerMarkdownLanguageFeatures( registerDefinitionSupport(selector, referencesProvider), registerDiagnosticSupport(selector, workspace, linkProvider, commandManager, referencesProvider, tocProvider, logger), registerDocumentLinkSupport(selector, linkProvider), - registerDocumentSymbolSupport(selector, tocProvider, logger), registerDropIntoEditorSupport(selector), registerFindFileReferenceSupport(commandManager, referencesProvider), - registerFoldingSupport(selector, parser, tocProvider), registerPasteSupport(selector), registerPathCompletionSupport(selector, workspace, parser, linkProvider), registerReferencesSupport(selector, referencesProvider), registerRenameSupport(selector, workspace, referencesProvider, parser.slugifier), - registerSmartSelectSupport(selector, parser, tocProvider), registerWorkspaceSymbolSupport(workspace, symbolProvider), ); } diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index d7a82d2aac3..f403b8ec7d6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -242,7 +242,9 @@ export abstract class DiagnosticReporter extends Disposable { public abstract delete(uri: vscode.Uri): void; - public abstract areDiagnosticsEnabled(uri: vscode.Uri): boolean; + public abstract isOpen(uri: vscode.Uri): boolean; + + public abstract getOpenDocuments(): ITextDocument[]; public addWorkItem(promise: Promise): Promise { this.pending.add(promise); @@ -256,6 +258,7 @@ export abstract class DiagnosticReporter extends Disposable { } export class DiagnosticCollectionReporter extends DiagnosticReporter { + private readonly collection: vscode.DiagnosticCollection; constructor() { @@ -269,11 +272,11 @@ export class DiagnosticCollectionReporter extends DiagnosticReporter { } public set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void { - this.collection.set(uri, this.areDiagnosticsEnabled(uri) ? diagnostics : []); + this.collection.set(uri, this.isOpen(uri) ? diagnostics : []); } - public areDiagnosticsEnabled(uri: vscode.Uri): boolean { - const tabs = this.getAllTabResources(); + public isOpen(uri: vscode.Uri): boolean { + const tabs = this.getTabResources(); return tabs.has(uri); } @@ -281,7 +284,12 @@ export class DiagnosticCollectionReporter extends DiagnosticReporter { this.collection.delete(uri); } - private getAllTabResources(): ResourceMap { + public getOpenDocuments(): ITextDocument[] { + const tabs = this.getTabResources(); + return vscode.workspace.textDocuments.filter(doc => tabs.has(doc.uri)); + } + + private getTabResources(): ResourceMap { const openedTabDocs = new ResourceMap(); for (const group of vscode.window.tabGroups.all) { for (const tab of group.tabs) { @@ -365,7 +373,7 @@ export class DiagnosticManager extends Disposable { return this.reporter.addWorkItem( (async () => { const triggered = new ResourceMap>(); - for (const ref of await this.referencesProvider.getAllReferencesToFile(uri, noopToken)) { + for (const ref of await this.referencesProvider.getReferencesToFileInDocs(uri, this.reporter.getOpenDocuments(), noopToken)) { const file = ref.location.uri; if (!triggered.has(file)) { triggered.set(file, this.triggerDiagnostics(file)); @@ -398,7 +406,7 @@ export class DiagnosticManager extends Disposable { const doc = await this.workspace.getOrLoadMarkdownDocument(resource); if (doc) { await this.inFlightDiagnostics.trigger(doc.uri, async (token) => { - if (this.reporter.areDiagnosticsEnabled(doc.uri)) { + if (this.reporter.isOpen(doc.uri)) { const state = await this.recomputeDiagnosticState(doc, token); this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []); this.reporter.set(doc.uri, state.diagnostics); @@ -417,12 +425,7 @@ export class DiagnosticManager extends Disposable { this.inFlightDiagnostics.clear(); return this.reporter.addWorkItem( - (async () => { - // TODO: This pulls in all md files in the workspace. Instead we only care about opened text documents. - // Need a new way to handle that. - const allDocs = await this.workspace.getAllMarkdownDocuments(); - await Promise.all(Array.from(allDocs, doc => this.triggerDiagnostics(doc.uri))); - })() + Promise.all(Array.from(this.reporter.getOpenDocuments(), doc => this.triggerDiagnostics(doc.uri))) ); } @@ -511,17 +514,17 @@ export class DiagnosticComputer { const diagnostics: vscode.Diagnostic[] = []; for (const link of links) { if (link.href.kind === 'internal' - && link.source.text.startsWith('#') + && link.source.hrefText.startsWith('#') && link.href.path.toString() === doc.uri.toString() && link.href.fragment && !toc.lookup(link.href.fragment) ) { - if (!this.isIgnoredLink(options, link.source.text)) { + if (!this.isIgnoredLink(options, link.source.hrefText)) { diagnostics.push(new LinkDoesNotExistDiagnostic( link.source.hrefRange, localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), severity, - link.source.text)); + link.source.hrefText)); } } } @@ -553,7 +556,7 @@ export class DiagnosticComputer { const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments); // We've already validated our own fragment links in `validateOwnHeaderLinks` - const linkSet = new FileLinkMap(links.filter(link => !link.source.text.startsWith('#'))); + const linkSet = new FileLinkMap(links.filter(link => !link.source.hrefText.startsWith('#'))); if (linkSet.size === 0) { return []; } @@ -582,10 +585,10 @@ export class DiagnosticComputer { if (fragmentLinks.length) { const toc = await this.tocProvider.get(resolvedHrefPath); for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) { + if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.hrefText)) { const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); const range = link.source.fragmentRange?.with({ start: link.source.fragmentRange.start.translate(0, -1) }) ?? link.source.hrefRange; - diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.text)); + diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.hrefText)); } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts index 8c322ce9756..154baf8abcb 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts @@ -9,7 +9,7 @@ import * as uri from 'vscode-uri'; import { OpenDocumentLinkCommand } from '../commands/openDocumentLink'; import { ILogger } from '../logging'; import { IMdParser } from '../markdownEngine'; -import { ITextDocument } from '../types/textDocument'; +import { getLine, ITextDocument } from '../types/textDocument'; import { coalesce } from '../util/arrays'; import { noopToken } from '../util/cancellation'; import { Disposable } from '../util/dispose'; @@ -108,18 +108,34 @@ function getWorkspaceFolder(document: ITextDocument) { } export interface MdLinkSource { + /** + * The full range of the link. + */ + readonly range: vscode.Range; + + /** + * The file where the link is defined. + */ + readonly resource: vscode.Uri; + /** * The original text of the link destination in code. */ - readonly text: string; + readonly hrefText: string; /** * The original text of just the link's path in code. */ readonly pathText: string; - readonly resource: vscode.Uri; + /** + * The range of the path. + */ readonly hrefRange: vscode.Range; + + /** + * The range of the fragment within the path. + */ readonly fragmentRange: vscode.Range | undefined; } @@ -145,32 +161,37 @@ function extractDocumentLink( document: ITextDocument, pre: string, rawLink: string, - matchIndex: number | undefined + matchIndex: number, + fullMatch: string, ): MdLink | undefined { const isAngleBracketLink = rawLink.startsWith('<'); const link = stripAngleBrackets(rawLink); - const offset = (matchIndex || 0) + pre.length + (isAngleBracketLink ? 1 : 0); - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); + let linkTarget: ExternalHref | InternalHref | undefined; try { - const linkTarget = resolveLink(document, link); - if (!linkTarget) { - return undefined; - } - return { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; + linkTarget = resolveLink(document, link); } catch { return undefined; } + if (!linkTarget) { + return undefined; + } + + const linkStart = document.positionAt(matchIndex); + const linkEnd = linkStart.translate(0, fullMatch.length); + const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); + const hrefEnd = hrefStart.translate(0, link.length); + return { + kind: 'link', + href: linkTarget, + source: { + hrefText: link, + resource: document.uri, + range: new vscode.Range(linkStart, linkEnd), + hrefRange: new vscode.Range(hrefStart, hrefEnd), + ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), + } + }; } function getFragmentRange(text: string, start: vscode.Position, end: vscode.Position): vscode.Range | undefined { @@ -278,13 +299,28 @@ class NoLinkRanges { /** * Inline code spans where links should not be detected */ - public readonly inline: Map + public readonly inline: Map ) { } contains(position: vscode.Position): boolean { return this.multiline.some(interval => position.line >= interval[0] && position.line < interval[1]) || !!this.inline.get(position.line)?.some(inlineRange => inlineRange.contains(position)); } + + concatInline(inlineRanges: Iterable): NoLinkRanges { + const newInline = new Map(this.inline); + for (const range of inlineRanges) { + for (let line = range.start.line; line <= range.end.line; ++line) { + let entry = newInline.get(line); + if (!entry) { + entry = []; + newInline.set(line, entry); + } + entry.push(range); + } + } + return new NoLinkRanges(this.multiline, newInline); + } } /** @@ -302,9 +338,10 @@ export class MdLinkComputer { return []; } + const inlineLinks = Array.from(this.getInlineLinks(document, noLinkRanges)); return Array.from([ - ...this.getInlineLinks(document, noLinkRanges), - ...this.getReferenceLinks(document, noLinkRanges), + ...inlineLinks, + ...this.getReferenceLinks(document, noLinkRanges.concatInline(inlineLinks.map(x => x.source.range))), ...this.getLinkDefinitions(document, noLinkRanges), ...this.getAutoLinks(document, noLinkRanges), ]); @@ -313,13 +350,13 @@ export class MdLinkComputer { private *getInlineLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(linkPattern)) { - const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index); + const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index ?? 0, match[0]); if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange.start)) { yield matchLinkData; // Also check link destination for links for (const innerMatch of match[1].matchAll(linkPattern)) { - const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0)); + const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0), innerMatch[0]); if (innerData) { yield innerData; } @@ -328,77 +365,83 @@ export class MdLinkComputer { } } - private * getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { + private *getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); - for (const match of text.matchAll(autoLinkPattern)) { + const linkOffset = (match.index ?? 0); + const linkStart = document.positionAt(linkOffset); + if (noLinkRanges.contains(linkStart)) { + continue; + } + const link = match[1]; const linkTarget = resolveLink(document, link); - if (linkTarget) { - const offset = (match.index ?? 0) + 1; - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { - continue; - } - yield { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; + if (!linkTarget) { + continue; } + + const linkEnd = linkStart.translate(0, match[0].length); + const hrefStart = linkStart.translate(0, 1); + const hrefEnd = hrefStart.translate(0, link.length); + yield { + kind: 'link', + href: linkTarget, + source: { + hrefText: link, + resource: document.uri, + hrefRange: new vscode.Range(hrefStart, hrefEnd), + range: new vscode.Range(linkStart, linkEnd), + ...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd), + } + }; } } private *getReferenceLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(referenceLinkPattern)) { - let linkStart: vscode.Position; - let linkEnd: vscode.Position; + const linkStart = document.positionAt(match.index ?? 0); + if (noLinkRanges.contains(linkStart)) { + continue; + } + + let hrefStart: vscode.Position; + let hrefEnd: vscode.Position; let reference = match[4]; if (reference === '') { // [ref][], reference = match[3]; const offset = ((match.index ?? 0) + match[1].length) + 1; - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + reference.length); + hrefStart = document.positionAt(offset); + hrefEnd = document.positionAt(offset + reference.length); } else if (reference) { // [text][ref] const pre = match[2]; const offset = ((match.index ?? 0) + match[1].length) + pre.length; - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + reference.length); + hrefStart = document.positionAt(offset); + hrefEnd = document.positionAt(offset + reference.length); } else if (match[5]) { // [ref] reference = match[5]; const offset = ((match.index ?? 0) + match[1].length) + 1; - linkStart = document.positionAt(offset); - const line = document.lineAt(linkStart.line); + hrefStart = document.positionAt(offset); + const line = getLine(document, hrefStart.line); // See if link looks like a checkbox - const checkboxMatch = line.text.match(/^\s*[\-\*]\s*\[x\]/i); - if (checkboxMatch && linkStart.character <= checkboxMatch[0].length) { + const checkboxMatch = line.match(/^\s*[\-\*]\s*\[x\]/i); + if (checkboxMatch && hrefStart.character <= checkboxMatch[0].length) { continue; } - linkEnd = document.positionAt(offset + reference.length); + hrefEnd = document.positionAt(offset + reference.length); } else { continue; } - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { - continue; - } - + const linkEnd = linkStart.translate(0, match[0].length); yield { kind: 'link', source: { - text: reference, + hrefText: reference, pathText: reference, resource: document.uri, - hrefRange, + range: new vscode.Range(linkStart, linkEnd), + hrefRange: new vscode.Range(hrefStart, hrefEnd), fragmentRange: undefined, }, href: { @@ -412,44 +455,41 @@ export class MdLinkComputer { private *getLinkDefinitions(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable { const text = document.getText(); for (const match of text.matchAll(definitionPattern)) { - const pre = match[1]; - const reference = match[2]; - const link = match[3].trim(); - const offset = (match.index || 0) + pre.length; - - const refStart = document.positionAt((match.index ?? 0) + 1); - const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); - - let linkStart: vscode.Position; - let linkEnd: vscode.Position; - let text: string; - if (angleBracketLinkRe.test(link)) { - linkStart = document.positionAt(offset + 1); - linkEnd = document.positionAt(offset + link.length - 1); - text = link.substring(1, link.length - 1); - } else { - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + link.length); - text = link; - } - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange.start)) { + const offset = (match.index ?? 0); + const linkStart = document.positionAt(offset); + if (noLinkRanges.contains(linkStart)) { continue; } - const target = resolveLink(document, text); - if (target) { - yield { - kind: 'definition', - source: { - text: link, - resource: document.uri, - hrefRange, - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - }, - ref: { text: reference, range: refRange }, - href: target, - }; + + const pre = match[1]; + const reference = match[2]; + const rawLinkText = match[3].trim(); + const target = resolveLink(document, rawLinkText); + if (!target) { + continue; } + + const isAngleBracketLink = angleBracketLinkRe.test(rawLinkText); + const linkText = stripAngleBrackets(rawLinkText); + const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0)); + const hrefEnd = hrefStart.translate(0, linkText.length); + const hrefRange = new vscode.Range(hrefStart, hrefEnd); + + const refStart = linkStart.translate(0, 1); + const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); + const linkEnd = linkStart.translate(0, match[0].length); + yield { + kind: 'definition', + source: { + hrefText: linkText, + resource: document.uri, + range: new vscode.Range(linkStart, linkEnd), + hrefRange, + ...getLinkSourceFragmentInfo(document, rawLinkText, hrefStart, hrefEnd), + }, + ref: { text: reference, range: refRange }, + href: target, + }; } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentSymbols.ts b/extensions/markdown-language-features/src/languageFeatures/documentSymbols.ts index a048180a4c6..2152e7bd46c 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentSymbols.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentSymbols.ts @@ -75,11 +75,3 @@ export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider { return '#'.repeat(entry.level) + ' ' + entry.text; } } - -export function registerDocumentSymbolSupport( - selector: vscode.DocumentSelector, - tocProvider: MdTableOfContentsProvider, - logger: ILogger, -): vscode.Disposable { - return vscode.languages.registerDocumentSymbolProvider(selector, new MdDocumentSymbolProvider(tocProvider, logger)); -} diff --git a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts index 9a2ecb9aa8e..4eea510d812 100644 --- a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts +++ b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts @@ -33,7 +33,7 @@ export class FindFileReferencesCommand implements Command { location: vscode.ProgressLocation.Window, title: localize('progress.title', "Finding file references") }, async (_progress, token) => { - const references = await this.referencesProvider.getAllReferencesToFile(resource!, token); + const references = await this.referencesProvider.getReferencesToFileInWorkspace(resource!, token); const locations = references.map(ref => ref.location); const config = vscode.workspace.getConfiguration('references'); diff --git a/extensions/markdown-language-features/src/languageFeatures/folding.ts b/extensions/markdown-language-features/src/languageFeatures/folding.ts deleted file mode 100644 index f79df449b04..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/folding.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type Token = require('markdown-it/lib/token'); -import * as vscode from 'vscode'; -import { IMdParser } from '../markdownEngine'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; - -const rangeLimit = 5000; - -interface MarkdownItTokenWithMap extends Token { - map: [number, number]; -} - -export class MdFoldingProvider implements vscode.FoldingRangeProvider { - - constructor( - private readonly parser: IMdParser, - private readonly tocProvide: MdTableOfContentsProvider, - ) { } - - public async provideFoldingRanges( - document: ITextDocument, - _: vscode.FoldingContext, - _token: vscode.CancellationToken - ): Promise { - const foldables = await Promise.all([ - this.getRegions(document), - this.getHeaderFoldingRanges(document), - this.getBlockFoldingRanges(document) - ]); - return foldables.flat().slice(0, rangeLimit); - } - - private async getRegions(document: ITextDocument): Promise { - const tokens = await this.parser.tokenize(document); - const regionMarkers = tokens.filter(isRegionMarker) - .map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) })); - - const nestingStack: { line: number; isStart: boolean }[] = []; - return regionMarkers - .map(marker => { - if (marker.isStart) { - nestingStack.push(marker); - } else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) { - return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region); - } else { - // noop: invalid nesting (i.e. [end, start] or [start, end, end]) - } - return null; - }) - .filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region); - } - - private async getHeaderFoldingRanges(document: ITextDocument): Promise { - const toc = await this.tocProvide.getForDocument(document); - return toc.entries.map(entry => { - let endLine = entry.sectionLocation.range.end.line; - if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) { - endLine = endLine - 1; - } - return new vscode.FoldingRange(entry.line, endLine); - }); - } - - private async getBlockFoldingRanges(document: ITextDocument): Promise { - const tokens = await this.parser.tokenize(document); - const multiLineListItems = tokens.filter(isFoldableToken); - return multiLineListItems.map(listItem => { - const start = listItem.map[0]; - let end = listItem.map[1] - 1; - if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) { - end = end - 1; - } - return new vscode.FoldingRange(start, end, this.getFoldingRangeKind(listItem)); - }); - } - - private getFoldingRangeKind(listItem: Token): vscode.FoldingRangeKind | undefined { - return listItem.type === 'html_block' && listItem.content.startsWith('/.test(t); -const isEndRegion = (t: string) => /^\s*/.test(t); - -const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap => - !!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content)); - -const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => { - if (!token.map) { - return false; - } - - switch (token.type) { - case 'fence': - case 'list_item_open': - return token.map[1] > token.map[0]; - - case 'html_block': - if (isRegionMarker(token)) { - return false; - } - return token.map[1] > token.map[0] + 1; - - default: - return false; - } -}; - -export function registerFoldingSupport( - selector: vscode.DocumentSelector, - parser: IMdParser, - tocProvider: MdTableOfContentsProvider, -): vscode.Disposable { - return vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(parser, tocProvider)); -} diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts index 0a8aeaaf15c..82e28faf3a4 100644 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts @@ -7,8 +7,9 @@ import { dirname, resolve } from 'path'; import * as vscode from 'vscode'; import { IMdParser } from '../markdownEngine'; import { TableOfContents } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; +import { getLine, ITextDocument } from '../types/textDocument'; import { resolveUriToMarkdownFile } from '../util/openDocumentLink'; +import { Schemes } from '../util/schemes'; import { IMdWorkspace } from '../workspace'; import { MdLinkProvider } from './documentLinks'; @@ -166,7 +167,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv private readonly definitionPattern = /^\s*\[[\w\-]+\]:\s*([^\s]*)$/m; private getPathCompletionContext(document: ITextDocument, position: vscode.Position): CompletionContext | undefined { - const line = document.lineAt(position.line).text; + const line = getLine(document, position.line); const linePrefixText = line.slice(0, position.character); const lineSuffixText = line.slice(position.character); @@ -192,7 +193,8 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv const definitionLinkPrefixMatch = linePrefixText.match(this.definitionPattern); if (definitionLinkPrefixMatch) { - const prefix = definitionLinkPrefixMatch[1]; + const isAngleBracketLink = definitionLinkPrefixMatch[1].startsWith('<'); + const prefix = definitionLinkPrefixMatch[1].slice(isAngleBracketLink ? 1 : 0); if (this.refLooksLikeUrl(prefix)) { return undefined; } @@ -204,6 +206,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv linkTextStartPosition: position.translate({ characterDelta: -prefix.length }), linkSuffix: suffix ? suffix[0] : '', anchorInfo: this.getAnchorContext(prefix), + skipEncoding: isAngleBracketLink, }; } @@ -286,7 +289,13 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv const pathSegmentEnd = position.translate({ characterDelta: context.linkSuffix.length }); const replacementRange = new vscode.Range(pathSegmentStart, pathSegmentEnd); - const dirInfo = await this.workspace.readDirectory(parentDir); + let dirInfo: [string, vscode.FileType][]; + try { + dirInfo = await this.workspace.readDirectory(parentDir); + } catch { + return; + } + for (const [name, type] of dirInfo) { // Exclude paths that start with `.` if (name.startsWith('.')) { @@ -324,7 +333,7 @@ export class MdVsCodePathCompletionProvider implements vscode.CompletionItemProv private resolvePath(root: vscode.Uri, ref: string): vscode.Uri | undefined { try { - if (root.scheme === 'file') { + if (root.scheme === Schemes.file) { return vscode.Uri.file(resolve(dirname(root.fsPath), ref)); } else { return root.with({ diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index a0e2de31915..51b7e7b92a1 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -67,7 +67,6 @@ export type MdReference = MdLinkReference | MdHeaderReference; export class MdReferencesProvider extends Disposable { private readonly _linkCache: MdWorkspaceInfoCache; - private readonly _linkComputer: MdLinkComputer; public constructor( private readonly parser: IMdParser, @@ -77,8 +76,8 @@ export class MdReferencesProvider extends Disposable { ) { super(); - this._linkComputer = new MdLinkComputer(parser); - this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => this._linkComputer.getAllLinks(doc, noopToken))); + const linkComputer = new MdLinkComputer(parser); + this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => linkComputer.getAllLinks(doc, noopToken))); } public async getReferencesAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { @@ -97,11 +96,26 @@ export class MdReferencesProvider extends Disposable { } } - public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise { - this.logger.verbose('ReferencesProvider', `getAllReferencesToFile: ${resource}`); + public async getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken): Promise { + this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInWorkspace: ${resource}`); const allLinksInWorkspace = (await this._linkCache.values()).flat(); - return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined)); + if (token.isCancellationRequested) { + return []; + } + + return Array.from(this.findLinksToFile(resource, allLinksInWorkspace, undefined)); + } + + public async getReferencesToFileInDocs(resource: vscode.Uri, otherDocs: readonly ITextDocument[], token: vscode.CancellationToken): Promise { + this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInFiles: ${resource}`); + + const links = (await this._linkCache.getForDocs(otherDocs)).flat(); + if (token.isCancellationRequested) { + return []; + } + + return Array.from(this.findLinksToFile(resource, links, undefined)); } private async getReferencesToHeader(document: ITextDocument, header: TocEntry): Promise { @@ -137,7 +151,7 @@ export class MdReferencesProvider extends Disposable { } private async getReferencesToLinkAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const docLinks = await this._linkComputer.getAllLinks(document, token); + const docLinks = (await this._linkCache.getForDocs([document]))[0]; for (const link of docLinks) { if (link.kind === 'definition') { @@ -223,7 +237,7 @@ export class MdReferencesProvider extends Disposable { } } } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments - references.push(...this.findAllLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink)); + references.push(...this.findLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink)); } return references; @@ -238,14 +252,14 @@ export class MdReferencesProvider extends Disposable { || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath; } - private *findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable { - for (const link of allLinksInWorkspace) { + private *findLinksToFile(resource: vscode.Uri, links: readonly MdLink[], sourceLink: MdLink | undefined): Iterable { + for (const link of links) { if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) { continue; } // Exclude cases where the file is implicitly referencing itself - if (link.source.text.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { + if (link.source.hrefText.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { continue; } diff --git a/extensions/markdown-language-features/src/languageFeatures/rename.ts b/extensions/markdown-language-features/src/languageFeatures/rename.ts index 3bff38697c5..44870e33ff1 100644 --- a/extensions/markdown-language-features/src/languageFeatures/rename.ts +++ b/extensions/markdown-language-features/src/languageFeatures/rename.ts @@ -179,7 +179,7 @@ export class MdVsCodeRenameProvider extends Disposable implements vscode.RenameP if (ref.kind === 'link') { // Try to preserve style of existing links let newPath: string; - if (ref.link.source.text.startsWith('/')) { + if (ref.link.source.hrefText.startsWith('/')) { const root = resolveDocumentLink('/', ref.link.source.resource); newPath = '/' + path.relative(root.toString(true), rawNewFilePath.toString(true)); } else { diff --git a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts deleted file mode 100644 index 29f0e922559..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts +++ /dev/null @@ -1,258 +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 Token = require('markdown-it/lib/token'); -import * as vscode from 'vscode'; -import { IMdParser } from '../markdownEngine'; -import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents'; -import { ITextDocument } from '../types/textDocument'; - -interface MarkdownItTokenWithMap extends Token { - map: [number, number]; -} - -export class MdSmartSelect implements vscode.SelectionRangeProvider { - - constructor( - private readonly parser: IMdParser, - private readonly tocProvider: MdTableOfContentsProvider, - ) { } - - public async provideSelectionRanges(document: ITextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise { - const promises = await Promise.all(positions.map((position) => { - return this.provideSelectionRange(document, position, _token); - })); - return promises.filter(item => item !== undefined) as vscode.SelectionRange[]; - } - - private async provideSelectionRange(document: ITextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { - const headerRange = await this.getHeaderSelectionRange(document, position); - const blockRange = await this.getBlockSelectionRange(document, position, headerRange); - const inlineRange = await this.getInlineSelectionRange(document, position, blockRange); - return inlineRange || blockRange || headerRange; - } - private async getInlineSelectionRange(document: ITextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise { - return createInlineRange(document, position, blockRange); - } - - private async getBlockSelectionRange(document: ITextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise { - const tokens = await this.parser.tokenize(document); - const blockTokens = getBlockTokensForPosition(tokens, position, headerRange); - - if (blockTokens.length === 0) { - return undefined; - } - - let currentRange: vscode.SelectionRange | undefined = headerRange ? headerRange : createBlockRange(blockTokens.shift()!, document, position.line); - - for (let i = 0; i < blockTokens.length; i++) { - currentRange = createBlockRange(blockTokens[i], document, position.line, currentRange); - } - return currentRange; - } - - private async getHeaderSelectionRange(document: ITextDocument, position: vscode.Position): Promise { - const toc = await this.tocProvider.getForDocument(document); - - const headerInfo = getHeadersForPosition(toc.entries, position); - - const headers = headerInfo.headers; - - let currentRange: vscode.SelectionRange | undefined; - - for (let i = 0; i < headers.length; i++) { - currentRange = createHeaderRange(headers[i], i === headers.length - 1, headerInfo.headerOnThisLine, currentRange, getFirstChildHeader(document, headers[i], toc.entries)); - } - return currentRange; - } -} - -function getHeadersForPosition(toc: readonly TocEntry[], position: vscode.Position): { headers: TocEntry[]; headerOnThisLine: boolean } { - const enclosingHeaders = toc.filter(header => header.sectionLocation.range.start.line <= position.line && header.sectionLocation.range.end.line >= position.line); - const sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line)); - const onThisLine = toc.find(header => header.line === position.line) !== undefined; - return { - headers: sortedHeaders, - headerOnThisLine: onThisLine - }; -} - -function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean, onHeaderLine: boolean, parent?: vscode.SelectionRange, startOfChildRange?: vscode.Position): vscode.SelectionRange | undefined { - const range = header.sectionLocation.range; - const contentRange = new vscode.Range(range.start.translate(1), range.end); - if (onHeaderLine && isClosestHeaderToPosition && startOfChildRange) { - // selection was made on this header line, so select header and its content until the start of its first child - // then all of its content - return new vscode.SelectionRange(range.with(undefined, startOfChildRange), new vscode.SelectionRange(range, parent)); - } else if (onHeaderLine && isClosestHeaderToPosition) { - // selection was made on this header line and no children so expand to all of its content - return new vscode.SelectionRange(range, parent); - } else if (isClosestHeaderToPosition && startOfChildRange) { - // selection was made within content and has child so select content - // of this header then all content then header - return new vscode.SelectionRange(contentRange.with(undefined, startOfChildRange), new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(range, parent)))); - } else { - // not on this header line so select content then header - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(range, parent)); - } -} - -function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] { - const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token)); - if (enclosingTokens.length === 0) { - return []; - } - const sortedTokens = enclosingTokens.sort((token1, token2) => (token2.map[1] - token2.map[0]) - (token1.map[1] - token1.map[0])); - return sortedTokens; -} - -function createBlockRange(block: MarkdownItTokenWithMap, document: ITextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - if (block.type === 'fence') { - return createFencedRange(block, cursorLine, document, parent); - } else { - let startLine = document.lineAt(block.map[0]).isEmptyOrWhitespace ? block.map[0] + 1 : block.map[0]; - let endLine = startLine === block.map[1] ? block.map[1] : block.map[1] - 1; - if (block.type === 'paragraph_open' && block.map[1] - block.map[0] === 2) { - startLine = endLine = cursorLine; - } else if (isList(block) && document.lineAt(endLine).isEmptyOrWhitespace) { - endLine = endLine - 1; - } - const range = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text?.length ?? 0); - if (parent?.range.contains(range) && !parent.range.isEqual(range)) { - return new vscode.SelectionRange(range, parent); - } else if (parent?.range.isEqual(range)) { - return parent; - } else { - return new vscode.SelectionRange(range); - } - } -} - -function createInlineRange(document: ITextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const lineText = document.lineAt(cursorPosition.line).text; - const boldSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, parent); - const italicSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, parent); - let comboSelection: vscode.SelectionRange | undefined; - if (boldSelection && italicSelection && !boldSelection.range.isEqual(italicSelection.range)) { - if (boldSelection.range.contains(italicSelection.range)) { - comboSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, boldSelection); - } else if (italicSelection.range.contains(boldSelection.range)) { - comboSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, italicSelection); - } - } - const linkSelection = createLinkRange(lineText, cursorPosition.character, cursorPosition.line, comboSelection || boldSelection || italicSelection || parent); - const inlineCodeBlockSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, false, linkSelection || parent); - return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection; -} - -function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: ITextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange { - const startLine = token.map[0]; - const endLine = token.map[1] - 1; - const onFenceLine = cursorLine === startLine || cursorLine === endLine; - const fenceRange = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text.length); - const contentRange = endLine - startLine > 2 && !onFenceLine ? new vscode.Range(startLine + 1, 0, endLine - 1, document.lineAt(endLine - 1).text.length) : undefined; - if (contentRange) { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent)); - } else { - if (parent?.range.isEqual(fenceRange)) { - return parent; - } else { - return new vscode.SelectionRange(fenceRange, parent); - } - } -} - -function createBoldRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const regex = /\*\*([^*]+\*?[^*]+\*?[^*]+)\*\*/gim; - const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - if (matches.length) { - // should only be one match, so select first and index 0 contains the entire match - const bold = matches[0][0]; - const startIndex = lineText.indexOf(bold); - const cursorOnStars = cursorChar === startIndex || cursorChar === startIndex + 1 || cursorChar === startIndex + bold.length || cursorChar === startIndex + bold.length - 1; - const contentAndStars = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + bold.length), parent); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 2, cursorLine, startIndex + bold.length - 2), contentAndStars); - return cursorOnStars ? contentAndStars : content; - } - return undefined; -} - -function createOtherInlineRange(lineText: string, cursorChar: number, cursorLine: number, isItalic: boolean, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const italicRegexes = [/(?:[^*]+)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]+)/g, /^(?:[^*]*)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]*)$/g]; - let matches = []; - if (isItalic) { - matches = [...lineText.matchAll(italicRegexes[0])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - if (!matches.length) { - matches = [...lineText.matchAll(italicRegexes[1])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - } - } else { - matches = [...lineText.matchAll(/\`[^\`]*\`/g)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - } - if (matches.length) { - // should only be one match, so select first and select group 1 for italics because that contains just the italic section - // doesn't include the leading and trailing characters which are guaranteed to not be * so as not to be confused with bold - const match = isItalic ? matches[0][1] : matches[0][0]; - const startIndex = lineText.indexOf(match); - const cursorOnType = cursorChar === startIndex || cursorChar === startIndex + match.length; - const contentAndType = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + match.length), parent); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 1, cursorLine, startIndex + match.length - 1), contentAndType); - return cursorOnType ? contentAndType : content; - } - return undefined; -} - -function createLinkRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const regex = /(\[[^\(\)]*\])(\([^\[\]]*\))/g; - const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length > cursorChar); - - if (matches.length) { - // should only be one match, so select first and index 0 contains the entire match, so match = [text](url) - const link = matches[0][0]; - const linkRange = new vscode.SelectionRange(new vscode.Range(cursorLine, lineText.indexOf(link), cursorLine, lineText.indexOf(link) + link.length), parent); - - const linkText = matches[0][1]; - const url = matches[0][2]; - - // determine if cursor is within [text] or (url) in order to know which should be selected - const nearestType = cursorChar >= lineText.indexOf(linkText) && cursorChar < lineText.indexOf(linkText) + linkText.length ? linkText : url; - - const indexOfType = lineText.indexOf(nearestType); - // determine if cursor is on a bracket or paren and if so, return the [content] or (content), skipping over the content range - const cursorOnType = cursorChar === indexOfType || cursorChar === indexOfType + nearestType.length; - - const contentAndNearestType = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType, cursorLine, indexOfType + nearestType.length), linkRange); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType + 1, cursorLine, indexOfType + nearestType.length - 1), contentAndNearestType); - return cursorOnType ? contentAndNearestType : content; - } - return undefined; -} - -function isList(token: Token): boolean { - return token.type ? ['ordered_list_open', 'list_item_open', 'bullet_list_open'].includes(token.type) : false; -} - -function isBlockElement(token: Token): boolean { - return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type); -} - -function getFirstChildHeader(document: ITextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined { - let childRange: vscode.Position | undefined; - if (header && toc) { - const children = toc.filter(t => header.sectionLocation.range.contains(t.sectionLocation.range) && t.sectionLocation.range.start.line > header.sectionLocation.range.start.line).sort((t1, t2) => t1.line - t2.line); - if (children.length > 0) { - childRange = children[0].sectionLocation.range.start; - const lineText = document.lineAt(childRange.line - 1).text; - return childRange ? childRange.translate(-1, lineText.length) : undefined; - } - } - return undefined; -} - -export function registerSmartSelectSupport( - selector: vscode.DocumentSelector, - parser: IMdParser, - tocProvider: MdTableOfContentsProvider, -): vscode.Disposable { - return vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(parser, tocProvider)); -} diff --git a/extensions/markdown-language-features/src/logging.ts b/extensions/markdown-language-features/src/logging.ts index b29758278f7..48a2f3a7709 100644 --- a/extensions/markdown-language-features/src/logging.ts +++ b/extensions/markdown-language-features/src/logging.ts @@ -58,7 +58,7 @@ export class VsCodeOutputLogger extends Disposable implements ILogger { const now = new Date(); return String(now.getUTCHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') - + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + now.getMilliseconds(); + + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0'); } private updateConfiguration(): void { diff --git a/extensions/markdown-language-features/src/tableOfContents.ts b/extensions/markdown-language-features/src/tableOfContents.ts index b1e3e25c8d3..5e0a0cc3684 100644 --- a/extensions/markdown-language-features/src/tableOfContents.ts +++ b/extensions/markdown-language-features/src/tableOfContents.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { ILogger } from './logging'; import { IMdParser } from './markdownEngine'; import { githubSlugifier, Slug, Slugifier } from './slugify'; -import { ITextDocument } from './types/textDocument'; +import { getLine, ITextDocument } from './types/textDocument'; import { Disposable } from './util/dispose'; import { isMarkdownFile } from './util/file'; import { Schemes } from './util/schemes'; @@ -108,9 +108,9 @@ export class TableOfContents { } const lineNumber = heading.map[0]; - const line = document.lineAt(lineNumber); + const line = getLine(document, lineNumber); - let slug = parser.slugifier.fromHeading(line.text); + let slug = parser.slugifier.fromHeading(line); const existingSlugEntry = existingSlugEntries.get(slug.value); if (existingSlugEntry) { ++existingSlugEntry.count; @@ -120,14 +120,14 @@ export class TableOfContents { } const headerLocation = new vscode.Location(document.uri, - new vscode.Range(lineNumber, 0, lineNumber, line.text.length)); + new vscode.Range(lineNumber, 0, lineNumber, line.length)); const headerTextLocation = new vscode.Location(document.uri, - new vscode.Range(lineNumber, line.text.match(/^#+\s*/)?.[0].length ?? 0, lineNumber, line.text.length - (line.text.match(/\s*#*$/)?.[0].length ?? 0))); + new vscode.Range(lineNumber, line.match(/^#+\s*/)?.[0].length ?? 0, lineNumber, line.length - (line.match(/\s*#*$/)?.[0].length ?? 0))); toc.push({ slug, - text: TableOfContents.getHeaderText(line.text), + text: TableOfContents.getHeaderText(line), level: TableOfContents.getHeaderLevel(heading.markup), line: lineNumber, sectionLocation: headerLocation, // Populated in next steps @@ -151,7 +151,7 @@ export class TableOfContents { sectionLocation: new vscode.Location(document.uri, new vscode.Range( entry.sectionLocation.range.start, - new vscode.Position(endLine, document.lineAt(endLine).text.length))) + new vscode.Position(endLine, getLine(document, endLine).length))) }; }); } diff --git a/extensions/markdown-language-features/src/test/definitionProvider.test.ts b/extensions/markdown-language-features/src/test/definitionProvider.test.ts index 8c1a51223a8..66c8919b5ba 100644 --- a/extensions/markdown-language-features/src/test/definitionProvider.test.ts +++ b/extensions/markdown-language-features/src/test/definitionProvider.test.ts @@ -10,17 +10,19 @@ import { MdVsCodeDefinitionProvider } from '../languageFeatures/definitions'; import { MdReferencesProvider } from '../languageFeatures/references'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { noopToken } from '../util/cancellation'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { joinLines, workspacePath } from './util'; +import { joinLines, withStore, workspacePath } from './util'; -function getDefinition(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) { +function getDefinition(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) { const engine = createNewMarkdownEngine(); - const referencesProvider = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); const provider = new MdVsCodeDefinitionProvider(referencesProvider); return provider.provideDefinition(doc, pos, noopToken); } @@ -46,31 +48,33 @@ function assertDefinitionsEqual(actualDef: vscode.Definition, ...expectedDefs: { } suite('markdown: Go to definition', () => { - test('Should not return definition when on link text', async () => { + test('Should not return definition when on link text', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `[ref](#abc)`, `[ref]: http://example.com`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const defs = await getDefinition(doc, new vscode.Position(0, 1), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(0, 1), workspace); assert.deepStrictEqual(defs, undefined); - }); + })); - test('Should find definition links within file from link', async () => { + test('Should find definition links within file from link', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, // trigger here ``, `[abc]: https://example.com`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 2 }, ); - }); + })); - test('Should find definition links using shorthand', async () => { + test('Should find definition links using shorthand', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[ref]`, // trigger 1 @@ -79,59 +83,62 @@ suite('markdown: Go to definition', () => { ``, `[ref]: /Hello.md` // trigger 3 )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); { - const defs = await getDefinition(doc, new vscode.Position(0, 2), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(0, 2), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 4 }, ); } { - const defs = await getDefinition(doc, new vscode.Position(2, 7), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(2, 7), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 4 }, ); } { - const defs = await getDefinition(doc, new vscode.Position(4, 2), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(4, 2), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 4 }, ); } - }); + })); - test('Should find definition links within file from definition', async () => { + test('Should find definition links within file from definition', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, ``, `[abc]: https://example.com`, // trigger here )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const defs = await getDefinition(doc, new vscode.Position(2, 3), new InMemoryMdWorkspace([doc])); + const defs = await getDefinition(store, doc, new vscode.Position(2, 3), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 2 }, ); - }); + })); - test('Should not find definition links across files', async () => { + test('Should not find definition links across files', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, ``, `[abc]: https://example.com`, )); - - const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(workspacePath('other.md'), joinLines( `[link 1][abc]`, ``, - `[abc]: https://example.com?bad`, + `[abc]: https://example.com?bad` )) ])); + + const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace); assertDefinitionsEqual(defs!, { uri: docUri, line: 2 }, ); - }); + })); }); diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index 3c8a8aed8ef..020e745a2d3 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -10,15 +10,16 @@ import { DiagnosticCollectionReporter, DiagnosticComputer, DiagnosticConfigurati import { MdLinkProvider } from '../languageFeatures/documentLinks'; import { MdReferencesProvider } from '../languageFeatures/references'; import { MdTableOfContentsProvider } from '../tableOfContents'; +import { ITextDocument } from '../types/textDocument'; import { noopToken } from '../util/cancellation'; -import { disposeAll } from '../util/dispose'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { ResourceMap } from '../util/resourceMap'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { assertRangeEqual, joinLines, workspacePath } from './util'; +import { assertRangeEqual, joinLines, withStore, workspacePath } from './util'; const defaultDiagnosticsOptions = Object.freeze({ enabled: true, @@ -29,10 +30,10 @@ const defaultDiagnosticsOptions = Object.freeze({ ignoreLinks: [], }); -async function getComputedDiagnostics(doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial = {}): Promise { +async function getComputedDiagnostics(store: DisposableStore, doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial = {}): Promise { const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine, workspace, nulLogger); - const tocProvider = new MdTableOfContentsProvider(engine, workspace, nulLogger); + const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger)); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); const computer = new DiagnosticComputer(workspace, linkProvider, tocProvider); return ( await computer.getDiagnostics(doc, { ...defaultDiagnosticsOptions, ...options, }, noopToken) @@ -79,6 +80,12 @@ class MemoryDiagnosticReporter extends DiagnosticReporter { private readonly diagnostics = new ResourceMap(); + constructor( + private readonly workspace: InMemoryMdWorkspace, + ) { + super(); + } + override dispose(): void { super.clear(); this.clear(); @@ -93,7 +100,7 @@ class MemoryDiagnosticReporter extends DiagnosticReporter { this.diagnostics.set(uri, diagnostics); } - areDiagnosticsEnabled(_uri: vscode.Uri): boolean { + isOpen(_uri: vscode.Uri): boolean { return true; } @@ -104,35 +111,41 @@ class MemoryDiagnosticReporter extends DiagnosticReporter { get(uri: vscode.Uri): readonly vscode.Diagnostic[] { return orderDiagnosticsByRange(this.diagnostics.get(uri) ?? []); } + + getOpenDocuments(): ITextDocument[] { + return this.workspace.values(); + } } suite('markdown: Diagnostic Computer', () => { - test('Should not return any diagnostics for empty document', async () => { + test('Should not return any diagnostics for empty document', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `text`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assert.deepStrictEqual(diagnostics, []); - }); + })); - test('Should generate diagnostic for link to file that does not exist', async () => { + test('Should generate diagnostic for link to file that does not exist', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `[bad](/no/such/file.md)`, `[good](/doc.md)`, `[good-ref]: /doc.md`, `[bad-ref]: /no/such/file.md`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(0, 6, 0, 22), new vscode.Range(3, 11, 3, 27), ]); - }); + })); - test('Should generate diagnostics for links to header that does not exist in current file', async () => { + test('Should generate diagnostics for links to header that does not exist in current file', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `[good](#good-header)`, `# Good Header`, @@ -141,15 +154,16 @@ suite('markdown: Diagnostic Computer', () => { `[good-ref]: #good-header`, `[bad-ref]: #no-such-header`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(2, 6, 2, 21), new vscode.Range(5, 11, 5, 26), ]); - }); + })); - test('Should generate diagnostics for links to non-existent headers in other files', async () => { + test('Should generate diagnostics for links to non-existent headers in other files', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `# My header`, `[good](#my-header)`, @@ -163,13 +177,13 @@ suite('markdown: Diagnostic Computer', () => { `# Other header`, )); - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1, doc2])); + const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1, doc2])); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(5, 14, 5, 35), ]); - }); + })); - test('Should support links both with and without .md file extension', async () => { + test('Should support links both with and without .md file extension', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `# My header`, `[good](#my-header)`, @@ -178,188 +192,182 @@ suite('markdown: Diagnostic Computer', () => { `[good](/doc#my-header)`, `[good](doc#my-header)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should generate diagnostics for non-existent link reference', async () => { + test('Should generate diagnostics for non-existent link reference', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `[good link][good]`, `[bad link][no-such]`, ``, `[good]: http://example.com`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(1, 11, 1, 18), ]); - }); + })); - test('Should not generate diagnostics when validate is disabled', async () => { + test('Should not generate diagnostics when validate is disabled', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](#no-such-header)`, `[text][no-such-ref]`, )); - - const workspace = new InMemoryMdWorkspace([doc1]); - const diagnostics = await getComputedDiagnostics(doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri)); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri)); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should not generate diagnostics for email autolink', async () => { + test('Should not generate diagnostics for email autolink', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `a c`, )); - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1])); + const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1])); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should not generate diagnostics for html tag that looks like an autolink', async () => { + test('Should not generate diagnostics for html tag that looks like an autolink', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `a b c`, `a b c`, )); - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1])); + const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1])); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should allow ignoring invalid file link using glob', async () => { + test('Should allow ignoring invalid file link using glob', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file)`, `![img](/no-such-file)`, `[text]: /no-such-file`, )); - - const workspace = new InMemoryMdWorkspace([doc1]); - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] }); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should be able to disable fragment validation for external files', async () => { + test('Should be able to disable fragment validation for external files', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/doc2.md#no-such)`, )); const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Disabling own fragment validation should also disable path fragment validation by default', async () => { + test('Disabling own fragment validation should also disable path fragment validation by default', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[b](#no-head)`, `![i](/doc2.md#no-such)`, )); const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); { - const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore }); assertDiagnosticsEqual(diagnostics, []); } { // But we should be able to override the default - const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning }); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(1, 13, 1, 21), ]); } - }); + })); - test('ignoreLinks should allow skipping link to non-existent file', async () => { + test('ignoreLinks should allow skipping link to non-existent file', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const workspace = new InMemoryMdWorkspace([doc1]); - - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('ignoreLinks should not consider link fragment', async () => { + test('ignoreLinks should not consider link fragment', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const workspace = new InMemoryMdWorkspace([doc1]); - - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('ignoreLinks should support globs', async () => { + test('ignoreLinks should support globs', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/images/aaa.png)`, `![i](/images/sub/bbb.png)`, `![i](/images/sub/sub2/ccc.png)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const workspace = new InMemoryMdWorkspace([doc1]); - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/images/**/*.png'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/images/**/*.png'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('ignoreLinks should support ignoring header', async () => { + test('ignoreLinks should support ignoring header', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](#no-such)`, )); - const workspace = new InMemoryMdWorkspace([doc1]); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['#no-such'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['#no-such'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('ignoreLinks should support ignoring header in file', async () => { + test('ignoreLinks should support ignoring header in file', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/doc2.md#no-such)`, )); const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); + const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); { - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] }); assertDiagnosticsEqual(diagnostics, []); } { - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md#*'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#*'] }); assertDiagnosticsEqual(diagnostics, []); } - }); + })); - test('ignoreLinks should support ignore header links if file is ignored', async () => { + test('ignoreLinks should support ignore header links if file is ignored', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/doc2.md#no-such)`, )); const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should not detect checkboxes as invalid links', async () => { + test('Should not detect checkboxes as invalid links', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `- [x]`, `- [X]`, `- [ ]`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const workspace = new InMemoryMdWorkspace([doc1]); - - const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md'] }); + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] }); assertDiagnosticsEqual(diagnostics, []); - }); + })); - test('Should detect invalid links with titles', async () => { + test('Should detect invalid links with titles', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[link]( "text")`, `[link]( 'text')`, @@ -368,7 +376,9 @@ suite('markdown: Diagnostic Computer', () => { `[link](no-such.md 'text')`, `[link](no-such.md (text))`, )); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(diagnostics, [ new vscode.Range(0, 8, 0, 18), new vscode.Range(1, 8, 1, 18), @@ -377,36 +387,36 @@ suite('markdown: Diagnostic Computer', () => { new vscode.Range(4, 7, 4, 17), new vscode.Range(5, 7, 5, 17), ]); - }); + })); - test('Should generate diagnostics for non-existent header using file link to own file', async () => { + test('Should generate diagnostics for non-existent header using file link to own file', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines( `[bad](doc.md#no-such)`, `[bad](doc#no-such)`, `[bad](/sub/doc.md#no-such)`, `[bad](/sub/doc#no-such)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc])); + const diagnostics = await getComputedDiagnostics(store, doc, workspace); assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [ new vscode.Range(0, 12, 0, 20), new vscode.Range(1, 9, 1, 17), new vscode.Range(2, 17, 2, 25), new vscode.Range(3, 14, 3, 22), ]); - }); + })); - test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', async () => { + test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', withStore(async (store) => { const doc1 = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines( `[bad](doc.md#no-such)`, `[bad](doc#no-such)`, `[bad](/sub/doc.md#no-such)`, `[bad](/sub/doc#no-such)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1])); - const workspace = new InMemoryMdWorkspace([doc1]); - - const diagnostics = await getComputedDiagnostics(doc1, workspace, { + const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning, }); @@ -416,31 +426,22 @@ suite('markdown: Diagnostic Computer', () => { new vscode.Range(2, 17, 2, 25), new vscode.Range(3, 14, 3, 22), ]); - }); + })); }); suite('Markdown: Diagnostics manager', () => { - const _disposables: vscode.Disposable[] = []; - - setup(() => { - disposeAll(_disposables); - }); - - teardown(() => { - disposeAll(_disposables); - }); - function createDiagnosticsManager( + store: DisposableStore, workspace: IMdWorkspace, configuration = new MemoryDiagnosticConfiguration({}), reporter: DiagnosticReporter = new DiagnosticCollectionReporter(), ) { const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine, workspace, nulLogger); - const tocProvider = new MdTableOfContentsProvider(engine, workspace, nulLogger); - const referencesProvider = new MdReferencesProvider(engine, workspace, tocProvider, nulLogger); - const manager = new DiagnosticManager( + const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger)); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); + const manager = store.add(new DiagnosticManager( workspace, new DiagnosticComputer(workspace, linkProvider, tocProvider), configuration, @@ -448,27 +449,26 @@ suite('Markdown: Diagnostics manager', () => { referencesProvider, tocProvider, nulLogger, - 0); - _disposables.push(linkProvider, tocProvider, referencesProvider, manager); + 0)); return manager; } - test('Changing enable/disable should recompute diagnostics', async () => { + test('Changing enable/disable should recompute diagnostics', withStore(async (store) => { const doc1Uri = workspacePath('doc1.md'); const doc2Uri = workspacePath('doc2.md'); - const workspace = new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(doc1Uri, joinLines( `[text](#no-such-1)`, )), new InMemoryDocument(doc2Uri, joinLines( `[text](#no-such-2)`, )) - ]); + ])); - const reporter = new MemoryDiagnosticReporter(); + const reporter = store.add(new MemoryDiagnosticReporter(workspace)); const config = new MemoryDiagnosticConfiguration({ enabled: true }); - const manager = createDiagnosticsManager(workspace, config, reporter); + const manager = createDiagnosticsManager(store, workspace, config, reporter); await manager.ready; // Check initial state (Enabled) @@ -495,9 +495,9 @@ suite('Markdown: Diagnostics manager', () => { assertDiagnosticsEqual(reporter.get(doc2Uri), [ new vscode.Range(0, 7, 0, 17), ]); - }); + })); - test('Should revalidate linked files when header changes', async () => { + test('Should revalidate linked files when header changes', withStore(async (store) => { const doc1Uri = workspacePath('doc1.md'); const doc1 = new InMemoryDocument(doc1Uri, joinLines( `[text](#no-such)`, @@ -509,11 +509,10 @@ suite('Markdown: Diagnostics manager', () => { `[text](#header)`, `[text](#no-such-2)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); + const reporter = store.add(new MemoryDiagnosticReporter(workspace)); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - const reporter = new MemoryDiagnosticReporter(); - - const manager = createDiagnosticsManager(workspace, new MemoryDiagnosticConfiguration({}), reporter); + const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter); await manager.ready; // Check initial state @@ -553,9 +552,9 @@ suite('Markdown: Diagnostics manager', () => { assertDiagnosticsEqual(reporter.get(doc2Uri), [ new vscode.Range(2, 7, 2, 17), ]); - }); + })); - test('Should revalidate linked files when file is deleted/created', async () => { + test('Should revalidate linked files when file is deleted/created', withStore(async (store) => { const doc1Uri = workspacePath('doc1.md'); const doc1 = new InMemoryDocument(doc1Uri, joinLines( `[text](/doc2.md)`, @@ -565,11 +564,10 @@ suite('Markdown: Diagnostics manager', () => { const doc2 = new InMemoryDocument(doc2Uri, joinLines( `# Header` )); + const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); + const reporter = store.add(new MemoryDiagnosticReporter(workspace)); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); - const reporter = new MemoryDiagnosticReporter(); - - const manager = createDiagnosticsManager(workspace, new MemoryDiagnosticConfiguration({}), reporter); + const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter); await manager.ready; // Check initial state @@ -589,5 +587,5 @@ suite('Markdown: Diagnostics manager', () => { workspace.createDocument(doc2); await reporter.waitPendingWork(); assertDiagnosticsEqual(reporter.get(doc1Uri), []); - }); + })); }); diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts index 17304f59868..0111980e849 100644 --- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts +++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts @@ -24,11 +24,17 @@ suite('Markdown: MdLinkComputer', () => { return linkProvider.getAllLinks(doc, noopToken); } - function assertLinksEqual(actualLinks: readonly MdLink[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actualLinks.length, expectedRanges.length); + function assertLinksEqual(actualLinks: readonly MdLink[], expected: ReadonlyArray) { + assert.strictEqual(actualLinks.length, expected.length); for (let i = 0; i < actualLinks.length; ++i) { - assertRangeEqual(actualLinks[i].source.hrefRange, expectedRanges[i], `Range ${i} to be equal`); + const exp = expected[i]; + if ('range' in exp) { + assertRangeEqual(actualLinks[i].source.hrefRange, exp.range, `Range ${i} to be equal`); + assert.strictEqual(actualLinks[i].source.hrefText, exp.sourceText, `Source text ${i} to be equal`); + } else { + assertRangeEqual(actualLinks[i].source.hrefRange, exp, `Range ${i} to be equal`); + } } } @@ -103,17 +109,23 @@ suite('Markdown: MdLinkComputer', () => { } }); - test('Should ignore texts in brackets inside link title (#150921)', async () => { + test('Should ignore bracketed text inside link title (#150921)', async () => { { - const links = await getLinksForFile('[some [inner bracket pairs] in title]()'); + const links = await getLinksForFile('[some [inner] in title](link)'); assertLinksEqual(links, [ - new vscode.Range(0, 39, 0, 43), + new vscode.Range(0, 24, 0, 28), ]); } { - const links = await getLinksForFile('[some [inner bracket pairs] in title](link)'); + const links = await getLinksForFile('[some [inner] in title]()'); assertLinksEqual(links, [ - new vscode.Range(0, 38, 0, 42) + new vscode.Range(0, 25, 0, 29), + ]); + } + { + const links = await getLinksForFile('[some [inner with space] in title](link)'); + assertLinksEqual(links, [ + new vscode.Range(0, 35, 0, 39), ]); } }); @@ -164,8 +176,8 @@ suite('Markdown: MdLinkComputer', () => { )); assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 9), - new vscode.Range(1, 6, 1, 8), + { range: new vscode.Range(0, 6, 0, 9), sourceText: 'b c' }, + { range: new vscode.Range(1, 6, 1, 8), sourceText: 'cd' }, ]); }); @@ -175,7 +187,7 @@ suite('Markdown: MdLinkComputer', () => { )); assertLinksEqual(links, [ - new vscode.Range(0, 9, 0, 28), + { range: new vscode.Range(0, 9, 0, 28), sourceText: 'https://example.com' }, ]); }); @@ -185,8 +197,8 @@ suite('Markdown: MdLinkComputer', () => { '[ref]: https://example.com', )); assertLinksEqual(links, [ - new vscode.Range(0, 1, 0, 4), - new vscode.Range(1, 7, 1, 26), + { range: new vscode.Range(0, 1, 0, 4), sourceText: 'ref' }, + { range: new vscode.Range(1, 7, 1, 26), sourceText: 'https://example.com' }, ]); }); diff --git a/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts deleted file mode 100644 index 66609941dc9..00000000000 --- a/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts +++ /dev/null @@ -1,98 +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 assert from 'assert'; -import 'mocha'; -import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbols'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryMdWorkspace } from './inMemoryWorkspace'; -import { nulLogger } from './nulLogging'; -import { workspacePath } from './util'; - - -function getSymbolsForFile(fileContents: string) { - const doc = new InMemoryDocument(workspacePath('test.md'), fileContents); - const workspace = new InMemoryMdWorkspace([doc]); - const engine = createNewMarkdownEngine(); - const provider = new MdDocumentSymbolProvider(new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); - return provider.provideDocumentSymbols(doc); -} - -suite('markdown.DocumentSymbolProvider', () => { - test('Should not return anything for empty document', async () => { - const symbols = await getSymbolsForFile(''); - assert.strictEqual(symbols.length, 0); - }); - - test('Should not return anything for document with no headers', async () => { - const symbols = await getSymbolsForFile('a\na'); - assert.strictEqual(symbols.length, 0); - }); - - test('Should not return anything for document with # but no real headers', async () => { - const symbols = await getSymbolsForFile('a#a\na#'); - assert.strictEqual(symbols.length, 0); - }); - - test('Should return single symbol for single header', async () => { - const symbols = await getSymbolsForFile('# h'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - }); - - test('Should not care about symbol level for single header', async () => { - const symbols = await getSymbolsForFile('### h'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '### h'); - }); - - test('Should put symbols of same level in flat list', async () => { - const symbols = await getSymbolsForFile('## h\n## h2'); - assert.strictEqual(symbols.length, 2); - assert.strictEqual(symbols[0].name, '## h'); - assert.strictEqual(symbols[1].name, '## h2'); - }); - - test('Should nest symbol of level - 1 under parent', async () => { - - const symbols = await getSymbolsForFile('# h\n## h2\n## h3'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 2); - assert.strictEqual(symbols[0].children[0].name, '## h2'); - assert.strictEqual(symbols[0].children[1].name, '## h3'); - }); - - test('Should nest symbol of level - n under parent', async () => { - const symbols = await getSymbolsForFile('# h\n#### h2'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 1); - assert.strictEqual(symbols[0].children[0].name, '#### h2'); - }); - - test('Should flatten children where lower level occurs first', async () => { - const symbols = await getSymbolsForFile('# h\n### h2\n## h3'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 2); - assert.strictEqual(symbols[0].children[0].name, '### h2'); - assert.strictEqual(symbols[0].children[1].name, '## h3'); - }); - - test('Should handle line separator in file. Issue #63749', async () => { - const symbols = await getSymbolsForFile(`# A -- foo - -# B -- bar`); - assert.strictEqual(symbols.length, 2); - assert.strictEqual(symbols[0].name, '# A'); - assert.strictEqual(symbols[1].name, '# B'); - }); -}); - diff --git a/extensions/markdown-language-features/src/test/fileReferences.test.ts b/extensions/markdown-language-features/src/test/fileReferences.test.ts index 3044d176a92..3b49e7790ea 100644 --- a/extensions/markdown-language-features/src/test/fileReferences.test.ts +++ b/extensions/markdown-language-features/src/test/fileReferences.test.ts @@ -9,18 +9,20 @@ import * as vscode from 'vscode'; import { MdReference, MdReferencesProvider } from '../languageFeatures/references'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { noopToken } from '../util/cancellation'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { joinLines, workspacePath } from './util'; +import { joinLines, withStore, workspacePath } from './util'; -function getFileReferences(resource: vscode.Uri, workspace: IMdWorkspace) { +function getFileReferences(store: DisposableStore, resource: vscode.Uri, workspace: IMdWorkspace) { const engine = createNewMarkdownEngine(); - const computer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); - return computer.getAllReferencesToFile(resource, noopToken); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); + return computer.getReferencesToFileInWorkspace(resource, noopToken); } function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) { @@ -37,82 +39,82 @@ function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRe suite('markdown: find file references', () => { - test('Should find basic references', async () => { + test('Should find basic references', withStore(async (store) => { const docUri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(docUri, joinLines( `# header`, `[link 1](./other.md)`, - `[link 2](./other.md)`, + `[link 2](./other.md)` )), new InMemoryDocument(otherUri, joinLines( `# header`, `pre`, `[link 3](./other.md)`, - `post`, + `post` )), ])); - assertReferencesEqual(refs!, + const refs = await getFileReferences(store, otherUri, workspace); + assertReferencesEqual(refs, { uri: docUri, line: 1 }, { uri: docUri, line: 2 }, { uri: otherUri, line: 2 }, ); - }); + })); - test('Should find references with and without file extensions', async () => { + test('Should find references with and without file extensions', withStore(async (store) => { const docUri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(docUri, joinLines( `# header`, `[link 1](./other.md)`, - `[link 2](./other)`, + `[link 2](./other)` )), new InMemoryDocument(otherUri, joinLines( `# header`, `pre`, `[link 3](./other.md)`, `[link 4](./other)`, - `post`, + `post` )), ])); - assertReferencesEqual(refs!, + const refs = await getFileReferences(store, otherUri, workspace); + assertReferencesEqual(refs, { uri: docUri, line: 1 }, { uri: docUri, line: 2 }, { uri: otherUri, line: 2 }, { uri: otherUri, line: 3 }, ); - }); + })); - test('Should find references with headers on links', async () => { + test('Should find references with headers on links', withStore(async (store) => { const docUri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(docUri, joinLines( `# header`, `[link 1](./other.md#sub-bla)`, - `[link 2](./other#sub-bla)`, + `[link 2](./other#sub-bla)` )), new InMemoryDocument(otherUri, joinLines( `# header`, `pre`, `[link 3](./other.md#sub-bla)`, `[link 4](./other#sub-bla)`, - `post`, + `post` )), ])); - assertReferencesEqual(refs!, + const refs = await getFileReferences(store, otherUri, workspace); + assertReferencesEqual(refs, { uri: docUri, line: 1 }, { uri: docUri, line: 2 }, { uri: otherUri, line: 2 }, { uri: otherUri, line: 3 }, ); - }); + })); }); diff --git a/extensions/markdown-language-features/src/test/foldingProvider.test.ts b/extensions/markdown-language-features/src/test/foldingProvider.test.ts deleted file mode 100644 index 5f5f3f874e6..00000000000 --- a/extensions/markdown-language-features/src/test/foldingProvider.test.ts +++ /dev/null @@ -1,228 +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 assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdFoldingProvider } from '../languageFeatures/folding'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryMdWorkspace } from './inMemoryWorkspace'; -import { nulLogger } from './nulLogging'; -import { joinLines } from './util'; - -const testFileName = vscode.Uri.file('test.md'); - -suite('markdown.FoldingProvider', () => { - test('Should not return anything for empty document', async () => { - const folds = await getFoldsForDocument(``); - assert.strictEqual(folds.length, 0); - }); - - test('Should not return anything for document without headers', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `**b** afas`, - `a#b`, - `a`, - )); - assert.strictEqual(folds.length, 0); - }); - - test('Should fold from header to end of document', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `# b`, - `c`, - `d`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('Should leave single newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - ``, - `# b`, - `y`, - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should collapse multiple newlines to single newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - ``, - ``, - ``, - `# b`, - `y` - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 4); - }); - - test('Should not collapse if there is no newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - `# b`, - `y`, - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold nested markers', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - ``, - `b`, - ``, - `b.a`, - ``, - `b`, - ``, - `b.b`, - ``, - `b`, - ``, - `a`, - )); - assert.strictEqual(folds.length, 3); - const [outer, first, second] = folds.sort((a, b) => a.start - b.start); - - assert.strictEqual(outer.start, 1); - assert.strictEqual(outer.end, 11); - assert.strictEqual(first.start, 3); - assert.strictEqual(first.end, 5); - assert.strictEqual(second.start, 7); - assert.strictEqual(second.end, 9); - }); - - test('Should fold from list to end of document', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `- b`, - `c`, - `d`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('lists folds should span multiple lines of content', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `- This list item\n spans multiple\n lines.`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('List should leave single blankline before new element', async () => { - const folds = await getFoldsForDocument(joinLines( - `- a`, - `a`, - ``, - ``, - `b` - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 0); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold fenced code blocks', async () => { - const folds = await getFoldsForDocument(joinLines( - `~~~ts`, - `a`, - `~~~`, - `b`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 0); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold fenced code blocks with yaml front matter', async () => { - const folds = await getFoldsForDocument(joinLines( - `---`, - `title: bla`, - `---`, - ``, - `~~~ts`, - `a`, - `~~~`, - ``, - `a`, - `a`, - `b`, - `a`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 4); - assert.strictEqual(firstFold.end, 6); - }); - - test('Should fold html blocks', async () => { - const folds = await getFoldsForDocument(joinLines( - `x`, - `
`, - ` fa`, - `
`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('Should fold html block comments', async () => { - const folds = await getFoldsForDocument(joinLines( - `x`, - `` - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment); - }); -}); - - -async function getFoldsForDocument(contents: string) { - const doc = new InMemoryDocument(testFileName, contents); - const workspace = new InMemoryMdWorkspace([doc]); - const engine = createNewMarkdownEngine(); - const provider = new MdFoldingProvider(engine, new MdTableOfContentsProvider(engine, workspace, nulLogger)); - return await provider.provideFoldingRanges(doc, {}, new vscode.CancellationTokenSource().token); -} diff --git a/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts b/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts index 0087e86674a..a383a73335a 100644 --- a/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts +++ b/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts @@ -7,23 +7,29 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { ITextDocument } from '../types/textDocument'; +import { Disposable } from '../util/dispose'; import { ResourceMap } from '../util/resourceMap'; import { IMdWorkspace } from '../workspace'; -export class InMemoryMdWorkspace implements IMdWorkspace { +export class InMemoryMdWorkspace extends Disposable implements IMdWorkspace { private readonly _documents = new ResourceMap(uri => uri.fsPath); constructor(documents: ITextDocument[]) { + super(); for (const doc of documents) { this._documents.set(doc.uri, doc); } } - public async getAllMarkdownDocuments() { + public values() { return Array.from(this._documents.values()); } + public async getAllMarkdownDocuments() { + return this.values(); + } + public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise { return this._documents.get(resource); } @@ -49,13 +55,13 @@ export class InMemoryMdWorkspace implements IMdWorkspace { return Array.from(files.entries()); } - private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event; - private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event; - private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event; public updateDocument(document: ITextDocument) { diff --git a/extensions/markdown-language-features/src/test/pathCompletion.test.ts b/extensions/markdown-language-features/src/test/pathCompletion.test.ts index d124461f102..f4ee75f2a74 100644 --- a/extensions/markdown-language-features/src/test/pathCompletion.test.ts +++ b/extensions/markdown-language-features/src/test/pathCompletion.test.ts @@ -292,4 +292,22 @@ suite('Markdown: Path completions', () => { { label: 'file.md', insertText: 'file.md' }, ]); }); + + test('Should support definition path with angle brackets', async () => { + const workspace = new InMemoryMdWorkspace([ + new InMemoryDocument(workspacePath('a.md'), ''), + new InMemoryDocument(workspacePath('b.md'), ''), + new InMemoryDocument(workspacePath('sub with space/file.md'), ''), + ]); + + const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( + `[def]: <./${CURSOR}>` + ), workspace); + + assertCompletionsEqual(completions, [ + { label: 'a.md', insertText: 'a.md' }, + { label: 'b.md', insertText: 'b.md' }, + { label: 'sub with space/', insertText: 'sub with space/' }, + ]); + }); }); diff --git a/extensions/markdown-language-features/src/test/references.test.ts b/extensions/markdown-language-features/src/test/references.test.ts index d4b5edfc828..087ede35239 100644 --- a/extensions/markdown-language-features/src/test/references.test.ts +++ b/extensions/markdown-language-features/src/test/references.test.ts @@ -9,19 +9,28 @@ import * as vscode from 'vscode'; import { MdReferencesProvider, MdVsCodeReferencesProvider } from '../languageFeatures/references'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { noopToken } from '../util/cancellation'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { joinLines, workspacePath } from './util'; +import { joinLines, withStore, workspacePath } from './util'; -function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) { +async function getReferences(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) { const engine = createNewMarkdownEngine(); - const computer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); const provider = new MdVsCodeReferencesProvider(computer); - return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken); + const refs = await provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken); + return refs.sort((a, b) => { + const pathCompare = a.uri.toString().localeCompare(b.uri.toString()); + if (pathCompare !== 0) { + return pathCompare; + } + return a.range.start.compareTo(b.range.start); + }); } function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expectedRefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) { @@ -42,26 +51,27 @@ function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expect } } -suite('markdown: find all references', () => { - test('Should not return references when not on header or link', async () => { +suite('Markdown: Find all references', () => { + test('Should not return references when not on header or link', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `# abc`, ``, `[link 1](#abc)`, `text`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); { - const refs = await getReferences(doc, new vscode.Position(1, 0), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(1, 0), workspace); assert.deepStrictEqual(refs, []); } { - const refs = await getReferences(doc, new vscode.Position(3, 2), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(3, 2), workspace); assert.deepStrictEqual(refs, []); } - }); + })); - test('Should find references from header within same file', async () => { + test('Should find references from header within same file', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# abc`, @@ -70,117 +80,128 @@ suite('markdown: find all references', () => { `[not link](#noabc)`, `[link 2](#abc)`, )); - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + + const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, { uri, line: 2 }, { uri, line: 4 }, ); - }); + })); - test('Should not return references when on link text', async () => { + test('Should not return references when on link text', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `[ref](#abc)`, `[ref]: http://example.com`, )); - const refs = await getReferences(doc, new vscode.Position(0, 1), new InMemoryMdWorkspace([doc])); - assert.deepStrictEqual(refs, []); - }); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - test('Should find references using normalized slug', async () => { + const refs = await getReferences(store, doc, new vscode.Position(0, 1), workspace); + assert.deepStrictEqual(refs, []); + })); + + test('Should find references using normalized slug', withStore(async (store) => { const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( `# a B c`, `[simple](#a-b-c)`, `[start underscore](#_a-b-c)`, `[different case](#a-B-C)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); { // Trigger header - const refs = await getReferences(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc])); + + const refs = await getReferences(store, doc, new vscode.Position(0, 0), workspace); assert.deepStrictEqual(refs!.length, 4); } { // Trigger on line 1 - const refs = await getReferences(doc, new vscode.Position(1, 12), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(1, 12), workspace); assert.deepStrictEqual(refs!.length, 4); } { // Trigger on line 2 - const refs = await getReferences(doc, new vscode.Position(2, 24), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(2, 24), workspace); assert.deepStrictEqual(refs!.length, 4); } { // Trigger on line 3 - const refs = await getReferences(doc, new vscode.Position(3, 20), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(3, 20), workspace); assert.deepStrictEqual(refs!.length, 4); } - }); + })); - test('Should find references from header across files', async () => { + test('Should find references from header across files', withStore(async (store) => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('sub', 'other.md'); - const other2Uri = workspacePath('other2.md'); + const other2Uri = workspacePath('zOther2.md'); const doc = new InMemoryDocument(docUri, joinLines( `# abc`, ``, `[link 1](#abc)`, )); - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([ + + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(other1Uri, joinLines( `[not link](#abc)`, `[not link](/doc.md#abz)`, - `[link](/doc.md#abc)`, + `[link](/doc.md#abc)` )), new InMemoryDocument(other2Uri, joinLines( `[not link](#abc)`, `[not link](./doc.md#abz)`, - `[link](./doc.md#abc)`, + `[link](./doc.md#abc)` )) ])); + const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace); + assertReferencesEqual(refs!, { uri: docUri, line: 0 }, // Header definition { uri: docUri, line: 2 }, { uri: other1Uri, line: 2 }, { uri: other2Uri, line: 2 }, ); - }); + })); - test('Should find references from header to link definitions ', async () => { + test('Should find references from header to link definitions ', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# abc`, ``, `[bla]: #abc` )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, // Header definition { uri, line: 2 }, ); - }); + })); - test('Should find header references from link definition', async () => { + test('Should find header references from link definition', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# A b C`, `[text][bla]`, `[bla]: #a-b-c`, // trigger here )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(2, 9), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(2, 9), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, // Header definition { uri, line: 2 }, ); - }); + })); - test('Should find references from link within same file', async () => { + test('Should find references from link within same file', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# abc`, @@ -189,40 +210,42 @@ suite('markdown: find all references', () => { `[not link](#noabc)`, `[link 2](#abc)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, // Header definition { uri, line: 2 }, { uri, line: 4 }, ); - }); + })); - test('Should find references from link across files', async () => { + test('Should find references from link across files', withStore(async (store) => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('sub', 'other.md'); - const other2Uri = workspacePath('other2.md'); + const other2Uri = workspacePath('zOther2.md'); const doc = new InMemoryDocument(docUri, joinLines( `# abc`, ``, `[link 1](#abc)`, )); - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(other1Uri, joinLines( `[not link](#abc)`, `[not link](/doc.md#abz)`, `[with ext](/doc.md#abc)`, - `[without ext](/doc#abc)`, + `[without ext](/doc#abc)` )), new InMemoryDocument(other2Uri, joinLines( `[not link](#abc)`, `[not link](./doc.md#abz)`, - `[link](./doc.md#abc)`, + `[link](./doc.md#abc)` )) ])); + const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, // Header definition { uri: docUri, line: 2 }, @@ -230,9 +253,9 @@ suite('markdown: find all references', () => { { uri: other1Uri, line: 3 }, // Other without ext { uri: other2Uri, line: 2 }, // Other2 ); - }); + })); - test('Should find references without requiring file extensions', async () => { + test('Should find references without requiring file extensions', withStore(async (store) => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('other.md'); @@ -241,7 +264,7 @@ suite('markdown: find all references', () => { ``, `[link 1](#a-b-c)`, )); - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(other1Uri, joinLines( `[not link](#a-b-c)`, @@ -249,10 +272,11 @@ suite('markdown: find all references', () => { `[with ext](/doc.md#a-b-c)`, `[without ext](/doc#a-b-c)`, `[rel with ext](./doc.md#a-b-c)`, - `[rel without ext](./doc#a-b-c)`, + `[rel without ext](./doc#a-b-c)` )), ])); + const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, // Header definition { uri: docUri, line: 2 }, @@ -261,9 +285,9 @@ suite('markdown: find all references', () => { { uri: other1Uri, line: 4 }, // Other relative link with ext { uri: other1Uri, line: 5 }, // Other relative link without ext ); - }); + })); - test('Should find references from link across files when triggered on link without file extension', async () => { + test('Should find references from link across files when triggered on link without file extension', withStore(async (store) => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('sub', 'other.md'); @@ -272,23 +296,24 @@ suite('markdown: find all references', () => { `[without ext](./sub/other.md#header)`, )); - const refs = await getReferences(doc, new vscode.Position(0, 23), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(other1Uri, joinLines( `pre`, `# header`, - `post`, + `post` )), ])); + const refs = await getReferences(store, doc, new vscode.Position(0, 23), workspace); assertReferencesEqual(refs!, - { uri: other1Uri, line: 1 }, // Header definition { uri: docUri, line: 0 }, { uri: docUri, line: 1 }, + { uri: other1Uri, line: 1 }, // Header definition ); - }); + })); - test('Should include header references when triggered on file link', async () => { + test('Should include header references when triggered on file link', withStore(async (store) => { const docUri = workspacePath('doc.md'); const otherUri = workspacePath('sub', 'other.md'); @@ -297,24 +322,24 @@ suite('markdown: find all references', () => { `[with ext](./sub/other#header)`, `[without ext](./sub/other.md#no-such-header)`, )); - - const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(otherUri, joinLines( `pre`, - `# header`, // Definition should not be included since we triggered on a file link - `post`, + `# header`, + `post` )), ])); + const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 1 }, { uri: docUri, line: 2 }, ); - }); + })); - test('Should not include refs from other file to own header', async () => { + test('Should not include refs from other file to own header', withStore(async (store) => { const docUri = workspacePath('doc.md'); const otherUri = workspacePath('sub', 'other.md'); @@ -322,7 +347,7 @@ suite('markdown: find all references', () => { `[other](./sub/other)`, // trigger here )); - const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(otherUri, joinLines( `# header`, // Definition should not be included since we triggered on a file link @@ -330,28 +355,30 @@ suite('markdown: find all references', () => { )), ])); + const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, ); - }); + })); - test('Should find explicit references to own file ', async () => { + test('Should find explicit references to own file ', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[bare](doc.md)`, // trigger here `[rel](./doc.md)`, `[abs](/doc.md)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, { uri, line: 1 }, { uri, line: 2 }, ); - }); + })); - test('Should support finding references to http uri', async () => { + test('Should support finding references to http uri', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[1](http://example.com)`, @@ -359,16 +386,17 @@ suite('markdown: find all references', () => { `[2](http://example.com)`, `[3]: http://example.com`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, { uri, line: 2 }, { uri, line: 3 }, ); - }); + })); - test('Should consider authority, scheme and paths when finding references to http uri', async () => { + test('Should consider authority, scheme and paths when finding references to http uri', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[1](http://example.com/cat)`, @@ -379,50 +407,53 @@ suite('markdown: find all references', () => { `[6](http://other.com/cat)`, `[7](https://example.com/cat)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, { uri, line: 4 }, ); - }); + })); - test('Should support finding references to http uri across files', async () => { + test('Should support finding references to http uri across files', withStore(async (store) => { const uri1 = workspacePath('doc.md'); const uri2 = workspacePath('doc2.md'); const doc = new InMemoryDocument(uri1, joinLines( `[1](http://example.com)`, `[3]: http://example.com`, )); - - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(uri2, joinLines( - `[other](http://example.com)`, + `[other](http://example.com)` )) ])); + + const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace); assertReferencesEqual(refs!, { uri: uri1, line: 0 }, { uri: uri1, line: 1 }, { uri: uri2, line: 0 }, ); - }); + })); - test('Should support finding references to autolinked http links', async () => { + test('Should support finding references to autolinked http links', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[1](http://example.com)`, ``, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace); assertReferencesEqual(refs!, { uri, line: 0 }, { uri, line: 1 }, ); - }); + })); - test('Should distinguish between references to file and to header within file', async () => { + test('Should distinguish between references to file and to header within file', withStore(async (store) => { const docUri = workspacePath('doc.md'); const other1Uri = workspacePath('sub', 'other.md'); @@ -435,14 +466,15 @@ suite('markdown: find all references', () => { `[link](/doc.md#abc)`, `[link no text](/doc#abc)`, )); - const workspace = new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, otherDoc, - ]); + ])); + { // Check refs to header fragment - const headerRefs = await getReferences(otherDoc, new vscode.Position(0, 16), workspace); - assertReferencesEqual(headerRefs!, + const headerRefs = await getReferences(store, otherDoc, new vscode.Position(0, 16), workspace); + assertReferencesEqual(headerRefs, { uri: docUri, line: 0 }, // Header definition { uri: docUri, line: 2 }, { uri: other1Uri, line: 0 }, @@ -451,23 +483,23 @@ suite('markdown: find all references', () => { } { // Check refs to file itself from link with ext - const fileRefs = await getReferences(otherDoc, new vscode.Position(0, 9), workspace); - assertReferencesEqual(fileRefs!, + const fileRefs = await getReferences(store, otherDoc, new vscode.Position(0, 9), workspace); + assertReferencesEqual(fileRefs, { uri: other1Uri, line: 0, endCharacter: 14 }, { uri: other1Uri, line: 1, endCharacter: 19 }, ); } { // Check refs to file itself from link without ext - const fileRefs = await getReferences(otherDoc, new vscode.Position(1, 17), workspace); - assertReferencesEqual(fileRefs!, + const fileRefs = await getReferences(store, otherDoc, new vscode.Position(1, 17), workspace); + assertReferencesEqual(fileRefs, { uri: other1Uri, line: 0 }, { uri: other1Uri, line: 1 }, ); } - }); + })); - test('Should support finding references to unknown file', async () => { + test('Should support finding references to unknown file', withStore(async (store) => { const uri1 = workspacePath('doc1.md'); const doc1 = new InMemoryDocument(uri1, joinLines( `![img](/images/more/image.png)`, @@ -480,17 +512,18 @@ suite('markdown: find all references', () => { `![img](/images/more/image.png)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - const refs = await getReferences(doc1, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc1, doc2])); + const refs = await getReferences(store, doc1, new vscode.Position(0, 10), workspace); assertReferencesEqual(refs!, { uri: uri1, line: 0 }, { uri: uri1, line: 2 }, { uri: uri2, line: 0 }, ); - }); + })); suite('Reference links', () => { - test('Should find reference links within file from link', async () => { + test('Should find reference links within file from link', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, // trigger here @@ -498,14 +531,16 @@ suite('markdown: find all references', () => { `[abc]: https://example.com`, )); - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + + const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, ); - }); + })); - test('Should find reference links using shorthand', async () => { + test('Should find reference links using shorthand', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[ref]`, // trigger 1 @@ -514,9 +549,10 @@ suite('markdown: find all references', () => { ``, `[ref]: /Hello.md` // trigger 3 )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); { - const refs = await getReferences(doc, new vscode.Position(0, 2), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 2), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, @@ -524,7 +560,7 @@ suite('markdown: find all references', () => { ); } { - const refs = await getReferences(doc, new vscode.Position(2, 7), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(2, 7), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, @@ -532,31 +568,32 @@ suite('markdown: find all references', () => { ); } { - const refs = await getReferences(doc, new vscode.Position(4, 2), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(4, 2), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, { uri: docUri, line: 4 }, ); } - }); + })); - test('Should find reference links within file from definition', async () => { + test('Should find reference links within file from definition', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, ``, `[abc]: https://example.com`, // trigger here )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(2, 3), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(2, 3), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, ); - }); + })); - test('Should not find reference links across files', async () => { + test('Should not find reference links across files', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `[link 1][abc]`, @@ -564,21 +601,23 @@ suite('markdown: find all references', () => { `[abc]: https://example.com`, )); - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(workspacePath('other.md'), joinLines( `[link 1][abc]`, ``, - `[abc]: https://example.com?bad`, + `[abc]: https://example.com?bad` )) ])); + + const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace); assertReferencesEqual(refs!, { uri: docUri, line: 0 }, { uri: docUri, line: 2 }, ); - }); + })); - test('Should not consider checkboxes as reference links', async () => { + test('Should not consider checkboxes as reference links', withStore(async (store) => { const docUri = workspacePath('doc.md'); const doc = new InMemoryDocument(docUri, joinLines( `- [x]`, @@ -587,9 +626,10 @@ suite('markdown: find all references', () => { ``, `[x]: https://example.com` )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const refs = await getReferences(doc, new vscode.Position(0, 4), new InMemoryMdWorkspace([doc])); + const refs = await getReferences(store, doc, new vscode.Position(0, 4), workspace); assert.strictEqual(refs?.length!, 0); - }); + })); }); }); diff --git a/extensions/markdown-language-features/src/test/rename.test.ts b/extensions/markdown-language-features/src/test/rename.test.ts index 97ac48d30c7..48e8d990321 100644 --- a/extensions/markdown-language-features/src/test/rename.test.ts +++ b/extensions/markdown-language-features/src/test/rename.test.ts @@ -11,31 +11,34 @@ import { MdVsCodeRenameProvider, MdWorkspaceEdit } from '../languageFeatures/ren import { githubSlugifier } from '../slugify'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { noopToken } from '../util/cancellation'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { assertRangeEqual, joinLines, workspacePath } from './util'; +import { assertRangeEqual, joinLines, withStore, workspacePath } from './util'; /** * Get prepare rename info. */ -function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace): Promise { +function prepareRename(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace): Promise { const engine = createNewMarkdownEngine(); - const referenceComputer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); - const renameProvider = new MdVsCodeRenameProvider(workspace, referenceComputer, githubSlugifier); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const referenceComputer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); + const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referenceComputer, githubSlugifier)); return renameProvider.prepareRename(doc, pos, noopToken); } /** * Get all the edits for the rename. */ -function getRenameEdits(doc: InMemoryDocument, pos: vscode.Position, newName: string, workspace: IMdWorkspace): Promise { +function getRenameEdits(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, newName: string, workspace: IMdWorkspace): Promise { const engine = createNewMarkdownEngine(); - const referencesProvider = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); - const renameProvider = new MdVsCodeRenameProvider(workspace, referencesProvider, githubSlugifier); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger)); + const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referencesProvider, githubSlugifier)); return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken); } @@ -91,73 +94,78 @@ suite('markdown: rename', () => { await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate(); }); - test('Rename on header should not include leading #', async () => { + test('Rename on header should not include leading #', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# abc` )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc])); + const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace); assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5)); - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 2, 0, 5), 'New Header') ] }); - }); + })); - test('Rename on header should include leading or trailing #s', async () => { + test('Rename on header should include leading or trailing #s', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `### abc ###` )); - const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + + const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace); assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7)); - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 4, 0, 7), 'New Header') ] }); - }); + })); - test('Rename on header should pick up links in doc', async () => { + test('Rename on header should pick up links in doc', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `### A b C`, // rename here `[text](#a-b-c)`, )); - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), ] }); - }); + })); - test('Rename on link should use slug for link', async () => { + test('Rename on link should use slug for link', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `### A b C`, `[text](#a-b-c)`, // rename here )); - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), ] }); - }); + })); - test('Rename on link definition should work', async () => { + test('Rename on link definition should work', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `### A b C`, @@ -165,7 +173,8 @@ suite('markdown: rename', () => { `[ref]: #a-b-c`// rename here )); - const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "New Header", new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), @@ -173,9 +182,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(2, 8, 2, 13), 'new-header'), ] }); - }); + })); - test('Rename on header should pick up links across files', async () => { + test('Rename on header should pick up links across files', withStore(async (store) => { const uri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); const doc = new InMemoryDocument(uri, joinLines( @@ -183,7 +192,7 @@ suite('markdown: rename', () => { `[text](#a-b-c)`, )); - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([ + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([ doc, new InMemoryDocument(otherUri, joinLines( `[text](#a-b-c)`, // Should not find this @@ -202,9 +211,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), ] }); - }); + })); - test('Rename on link should pick up links across files', async () => { + test('Rename on link should pick up links across files', withStore(async (store) => { const uri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); const doc = new InMemoryDocument(uri, joinLines( @@ -212,7 +221,7 @@ suite('markdown: rename', () => { `[text](#a-b-c)`, // rename here )); - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([ + const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([ doc, new InMemoryDocument(otherUri, joinLines( `[text](#a-b-c)`, // Should not find this @@ -231,9 +240,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), ] }); - }); + })); - test('Rename on link in other file should pick up all refs', async () => { + test('Rename on link in other file should pick up all refs', withStore(async (store) => { const uri = workspacePath('doc.md'); const otherUri = workspacePath('other.md'); const doc = new InMemoryDocument(uri, joinLines( @@ -263,7 +272,7 @@ suite('markdown: rename', () => { { // Rename on header with file extension - const edit = await getRenameEdits(otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryMdWorkspace([ + const edit = await getRenameEdits(store, otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryMdWorkspace([ doc, otherDoc ])); @@ -271,15 +280,15 @@ suite('markdown: rename', () => { } { // Rename on header without extension - const edit = await getRenameEdits(otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryMdWorkspace([ + const edit = await getRenameEdits(store, otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryMdWorkspace([ doc, otherDoc ])); assertEditsEqual(edit!, ...expectedEdits); } - }); + })); - test('Rename on reference should rename references and definition', async () => { + test('Rename on reference should rename references and definition', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text][ref]`, // rename here @@ -288,7 +297,8 @@ suite('markdown: rename', () => { `[ref]: https://example.com`, )); - const edit = await getRenameEdits(doc, new vscode.Position(0, 8), "new ref", new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 8), "new ref", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'), @@ -296,9 +306,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'), ] }); - }); + })); - test('Rename on definition should rename references and definitions', async () => { + test('Rename on definition should rename references and definitions', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text][ref]`, @@ -307,7 +317,8 @@ suite('markdown: rename', () => { `[ref]: https://example.com`, // rename here )); - const edit = await getRenameEdits(doc, new vscode.Position(3, 3), "new ref", new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(3, 3), "new ref", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'), @@ -315,9 +326,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'), ] }); - }); + })); - test('Rename on definition entry should rename header and references', async () => { + test('Rename on definition entry should rename header and references', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# a B c`, @@ -325,12 +336,13 @@ suite('markdown: rename', () => { `[direct](#a-b-c)`, `[ref]: #a-b-c`, // rename here )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const preparedInfo = await prepareRename(doc, new vscode.Position(3, 10), new InMemoryMdWorkspace([doc])); + const preparedInfo = await prepareRename(store, doc, new vscode.Position(3, 10), workspace); assert.strictEqual(preparedInfo!.placeholder, 'a B c'); assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13)); - const edit = await getRenameEdits(doc, new vscode.Position(3, 10), "x Y z", new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(3, 10), "x Y z", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'), @@ -338,50 +350,54 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'), ] }); - }); + })); - test('Rename should not be supported on link text', async () => { + test('Rename should not be supported on link text', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `# Header`, `[text](#header)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryMdWorkspace([doc]))); - }); + await assert.rejects(prepareRename(store, doc, new vscode.Position(1, 2), workspace)); + })); - test('Path rename should use file path as range', async () => { + test('Path rename should use file path as range', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](./doc.md)`, `[ref]: ./doc.md`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc])); + const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace); assert.strictEqual(info!.placeholder, './doc.md'); assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15)); - }); + })); - test('Path rename\'s range should excludes fragment', async () => { + test('Path rename\'s range should excludes fragment', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](./doc.md#some-header)`, `[ref]: ./doc.md#some-header`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc])); + const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace); assert.strictEqual(info!.placeholder, './doc.md'); assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15)); - }); + })); - test('Path rename should update file and all refs', async () => { + test('Path rename should update file and all refs', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](./doc.md)`, `[ref]: ./doc.md`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), './sub/newDoc.md', new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), './sub/newDoc.md', workspace); assertEditsEqual(edit!, { originalUri: uri, newUri: workspacePath('sub', 'newDoc.md'), @@ -391,16 +407,17 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './sub/newDoc.md'), ] }); - }); + })); - test('Path rename using absolute file path should anchor to workspace root', async () => { + test('Path rename using absolute file path should anchor to workspace root', withStore(async (store) => { const uri = workspacePath('sub', 'doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](/sub/doc.md)`, `[ref]: /sub/doc.md`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/newSub/newDoc.md', new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/newSub/newDoc.md', workspace); assertEditsEqual(edit!, { originalUri: uri, newUri: workspacePath('newSub', 'newDoc.md'), @@ -410,26 +427,28 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/newSub/newDoc.md'), ] }); - }); + })); - test('Path rename should use un-encoded paths as placeholder', async () => { + test('Path rename should use un-encoded paths as placeholder', withStore(async (store) => { const uri = workspacePath('sub', 'doc with spaces.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](/sub/doc%20with%20spaces.md)`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc])); + const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace); assert.strictEqual(info!.placeholder, '/sub/doc with spaces.md'); - }); + })); - test('Path rename should encode paths', async () => { + test('Path rename should encode paths', withStore(async (store) => { const uri = workspacePath('sub', 'doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](/sub/doc.md)`, `[ref]: /sub/doc.md`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', workspace); assertEditsEqual(edit!, { originalUri: uri, newUri: workspacePath('NEW sub', 'new DOC.md'), @@ -439,9 +458,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/NEW%20sub/new%20DOC.md'), ] }); - }); + })); - test('Path rename should work with unknown files', async () => { + test('Path rename should work with unknown files', withStore(async (store) => { const uri1 = workspacePath('doc1.md'); const doc1 = new InMemoryDocument(uri1, joinLines( `![img](/images/more/image.png)`, @@ -454,10 +473,12 @@ suite('markdown: rename', () => { `![img](/images/more/image.png)`, )); - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), '/img/test/new.png', new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc1, doc2 ])); + + const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), '/img/test/new.png', workspace); assertEditsEqual(edit!, // Should not have file edits since the files don't exist here { @@ -471,16 +492,17 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'), ] }); - }); + })); - test('Path rename should use .md extension on extension-less link', async () => { + test('Path rename should use .md extension on extension-less link', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text](/doc#header)`, `[ref]: /doc#other`, )); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/new File', new InMemoryMdWorkspace([doc])); + const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/new File', workspace); assertEditsEqual(edit!, { originalUri: uri, newUri: workspacePath('new File.md'), // Rename on disk should use file extension @@ -490,10 +512,10 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(1, 7, 1, 11), '/new%20File'), ] }); - }); + })); // TODO: fails on windows - test.skip('Path rename should use correctly resolved paths across files', async () => { + test.skip('Path rename should use correctly resolved paths across files', withStore(async (store) => { const uri1 = workspacePath('sub', 'doc.md'); const doc1 = new InMemoryDocument(uri1, joinLines( `[text](./doc.md)`, @@ -518,9 +540,11 @@ suite('markdown: rename', () => { `[ref]: /sub/doc.md`, )); - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), './new/new-doc.md', new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc1, doc2, doc3, doc4, ])); + + const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), './new/new-doc.md', workspace); assertEditsEqual(edit!, { originalUri: uri1, newUri: workspacePath('sub', 'new', 'new-doc.md'), @@ -545,9 +569,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/sub/new/new-doc.md'), ] }); - }); + })); - test('Path rename should resolve on links without prefix', async () => { + test('Path rename should resolve on links without prefix', withStore(async (store) => { const uri1 = workspacePath('sub', 'doc.md'); const doc1 = new InMemoryDocument(uri1, joinLines( `![text](sub2/doc3.md)`, @@ -561,9 +585,11 @@ suite('markdown: rename', () => { const uri3 = workspacePath('sub', 'sub2', 'doc3.md'); const doc3 = new InMemoryDocument(uri3, joinLines()); - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), 'sub2/cat.md', new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc1, doc2, doc3 ])); + + const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), 'sub2/cat.md', workspace); assertEditsEqual(edit!, { originalUri: workspacePath('sub', 'sub2', 'doc3.md'), newUri: workspacePath('sub', 'sub2', 'cat.md'), @@ -572,21 +598,22 @@ suite('markdown: rename', () => { }, { uri: uri2, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 24), 'sub/sub2/cat.md')] }); - }); + })); - test('Rename on link should use header text as placeholder', async () => { + test('Rename on link should use header text as placeholder', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `### a B c ###`, `[text](#a-b-c)`, )); - const info = await prepareRename(doc, new vscode.Position(1, 10), new InMemoryMdWorkspace([doc])); + const workspace = store.add(new InMemoryMdWorkspace([doc])); + const info = await prepareRename(store, doc, new vscode.Position(1, 10), workspace); assert.strictEqual(info!.placeholder, 'a B c'); assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13)); - }); + })); - test('Rename on http uri should work', async () => { + test('Rename on http uri should work', withStore(async (store) => { const uri1 = workspacePath('doc.md'); const uri2 = workspacePath('doc2.md'); const doc = new InMemoryDocument(uri1, joinLines( @@ -595,12 +622,14 @@ suite('markdown: rename', () => { ``, )); - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "https://example.com/sub", new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ doc, new InMemoryDocument(uri2, joinLines( - `[4](http://example.com)`, + `[4](http://example.com)` )) ])); + + const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "https://example.com/sub", workspace); assertEditsEqual(edit!, { uri: uri1, edits: [ new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'), @@ -612,9 +641,9 @@ suite('markdown: rename', () => { new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'), ] }); - }); + })); - test('Rename on definition path should update all references to path', async () => { + test('Rename on definition path should update all references to path', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[ref text][ref]`, @@ -622,22 +651,22 @@ suite('markdown: rename', () => { `[ref]: /file`, // rename here )); - const workspace = new InMemoryMdWorkspace([doc]); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const preparedInfo = await prepareRename(doc, new vscode.Position(2, 10), workspace); + const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 10), workspace); assert.strictEqual(preparedInfo!.placeholder, '/file'); assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); - const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "/newFile", workspace); + const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "/newFile", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/newFile'), new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/newFile'), ] }); - }); + })); - test('Rename on definition path where file exists should also update file', async () => { + test('Rename on definition path where file exists should also update file', withStore(async (store) => { const uri1 = workspacePath('doc.md'); const doc1 = new InMemoryDocument(uri1, joinLines( `[ref text][ref]`, @@ -648,13 +677,13 @@ suite('markdown: rename', () => { const uri2 = workspacePath('doc2.md'); const doc2 = new InMemoryDocument(uri2, joinLines()); - const workspace = new InMemoryMdWorkspace([doc1, doc2]); + const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2])); - const preparedInfo = await prepareRename(doc1, new vscode.Position(2, 10), workspace); + const preparedInfo = await prepareRename(store, doc1, new vscode.Position(2, 10), workspace); assert.strictEqual(preparedInfo!.placeholder, '/doc2'); assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); - const edit = await getRenameEdits(doc1, new vscode.Position(2, 10), "/new-doc", workspace); + const edit = await getRenameEdits(store, doc1, new vscode.Position(2, 10), "/new-doc", workspace); assertEditsEqual(edit!, { uri: uri1, edits: [ new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/new-doc'), @@ -664,9 +693,9 @@ suite('markdown: rename', () => { originalUri: uri2, newUri: workspacePath('new-doc.md') }); - }); + })); - test('Rename on definition path header should update all references to header', async () => { + test('Rename on definition path header should update all references to header', withStore(async (store) => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[ref text][ref]`, @@ -674,18 +703,18 @@ suite('markdown: rename', () => { `[ref]: /file#header`, // rename here )); - const workspace = new InMemoryMdWorkspace([doc]); + const workspace = store.add(new InMemoryMdWorkspace([doc])); - const preparedInfo = await prepareRename(doc, new vscode.Position(2, 16), workspace); + const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 16), workspace); assert.strictEqual(preparedInfo!.placeholder, 'header'); assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 13, 2, 19)); - const edit = await getRenameEdits(doc, new vscode.Position(2, 16), "New Header", workspace); + const edit = await getRenameEdits(store, doc, new vscode.Position(2, 16), "New Header", workspace); assertEditsEqual(edit!, { uri, edits: [ new vscode.TextEdit(new vscode.Range(1, 15, 1, 21), 'new-header'), new vscode.TextEdit(new vscode.Range(2, 13, 2, 19), 'new-header'), ] }); - }); + })); }); diff --git a/extensions/markdown-language-features/src/test/smartSelect.test.ts b/extensions/markdown-language-features/src/test/smartSelect.test.ts deleted file mode 100644 index eb0be03af34..00000000000 --- a/extensions/markdown-language-features/src/test/smartSelect.test.ts +++ /dev/null @@ -1,731 +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 assert from 'assert'; -import * as vscode from 'vscode'; -import { MdSmartSelect } from '../languageFeatures/smartSelect'; -import { MdTableOfContentsProvider } from '../tableOfContents'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryMdWorkspace } from './inMemoryWorkspace'; -import { nulLogger } from './nulLogging'; -import { CURSOR, getCursorPositions, joinLines } from './util'; - -const testFileName = vscode.Uri.file('test.md'); - -suite('markdown.SmartSelect', () => { - test('Smart select single word', async () => { - const ranges = await getSelectionRangesForDocument(`Hel${CURSOR}lo`); - assertNestedLineNumbersEqual(ranges![0], [0, 0]); - }); - - test('Smart select multi-line paragraph', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. `, - `For example, the[node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter]`, - `(https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).` - )); - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select paragraph', async () => { - const ranges = await getSelectionRangesForDocument(`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`); - - assertNestedLineNumbersEqual(ranges![0], [0, 0]); - }); - - test('Smart select html block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `

`, - `${CURSOR}VS Code in action`, - `

`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select header on header line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# Header${CURSOR}`, - `Hello`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 1]); - - }); - - test('Smart select single word w grandparent header on text line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `## ParentHeader`, - `# Header`, - `${CURSOR}Hello` - )); - - assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 2]); - }); - - test('Smart select html block w parent header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# Header`, - `${CURSOR}

`, - `VS Code in action`, - `

`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 3], [0, 3]); - }); - - test('Smart select fenced code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `~~~`, - `a${CURSOR}`, - `~~~`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- item 1`, - `- ${CURSOR}item 2`, - `- item 3`, - `- item 4`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [0, 3]); - }); - - test('Smart select list with fenced code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- item 1`, - `- ~~~`, - ` ${CURSOR}a`, - ` ~~~`, - `- item 3`, - `- item 4`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 5]); - }); - - test('Smart select multi cursor', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- ${CURSOR}item 1`, - `- ~~~`, - ` a`, - ` ~~~`, - `- ${CURSOR}item 3`, - `- item 4`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 0], [0, 5]); - assertNestedLineNumbersEqual(ranges![1], [4, 4], [0, 5]); - }); - - test('Smart select nested block quotes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `> item 1`, - `> item 2`, - `>> ${CURSOR}item 3`, - `>> item 4`)); - assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [0, 3]); - }); - - test('Smart select multi nested block quotes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `> item 1`, - `>> item 2`, - `>>> ${CURSOR}item 3`, - `>>>> item 4`)); - assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select subheader content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `## sub header 1`, - `${CURSOR}content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select subheader line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `## sub header 1${CURSOR}`, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select blank line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `${CURSOR} `, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 3]); - }); - - test('Smart select line between paragraphs', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `paragraph 1`, - `${CURSOR}`, - `paragraph 2`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select empty document', async () => { - const ranges = await getSelectionRangesForDocument(``, [new vscode.Position(0, 0)]); - assert.strictEqual(ranges!.length, 0); - }); - - test('Smart select fenced code block then list then subheader content then subheader then header content then header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - /* 00 */ `# main header 1`, - /* 01 */ `content 1`, - /* 02 */ `## sub header 1`, - /* 03 */ `- item 1`, - /* 04 */ `- ~~~`, - /* 05 */ ` ${CURSOR}a`, - /* 06 */ ` ~~~`, - /* 07 */ `- item 3`, - /* 08 */ `- item 4`, - /* 09 */ ``, - /* 10 */ `more content`, - /* 11 */ `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 8], [3, 10], [2, 10], [1, 10], [0, 10]); - }); - - test('Smart select list with one element without selecting child subheader', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - /* 00 */ `# main header 1`, - /* 01 */ ``, - /* 02 */ `- list ${CURSOR}`, - /* 03 */ ``, - /* 04 */ `## sub header`, - /* 05 */ ``, - /* 06 */ `content 2`, - /* 07 */ `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 3], [1, 6], [0, 6]); - }); - - test('Smart select content under header then subheaders and their content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main ${CURSOR}header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - ``, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 3], [0, 6]); - }); - - test('Smart select last blockquote element under header then subheaders and their content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> ${CURSOR}block`, - ``, - `paragraph`, - `## sub header`, - ``, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [4, 5], [2, 5], [1, 7], [1, 10], [0, 10]); - }); - - test('Smart select content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - `${CURSOR}`, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [11, 11], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select last line content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - ``, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `- ${CURSOR}content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select last line content after content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - ``, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2${CURSOR}`)); - - assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select fenced code block then list then rest of content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `- paragraph`, - `- ~~~`, - ` my`, - ` ${CURSOR}code`, - ` goes here`, - ` ~~~`, - `- content`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [9, 11], [8, 12], [8, 12], [7, 17], [1, 17], [0, 17]); - }); - - test('Smart select fenced code block then list then rest of content on fenced line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `- paragraph`, - `- ~~~${CURSOR}`, - ` my`, - ` code`, - ` goes here`, - ` ~~~`, - `- content`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]); - }); - - test('Smart select without multiple ranges', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - ``, - `- ${CURSOR}paragraph`, - `- content`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); - }); - - test('Smart select on second level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level 2`, - ` * level 1`, - ` * level ${CURSOR}1`, - `* level 0`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); - }); - - test('Smart select on third level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - ` * level 1`, - ` * level 1`, - `* level 0`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); - }); - - test('Smart select level 2 then level 1', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - `* level 1`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); - }); - - test('Smart select last list item', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- level 1`, - `- level 2`, - `- level 2`, - `- level ${CURSOR}1`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [0, 3]); - }); - - test('Smart select without multiple ranges', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - ``, - `- ${CURSOR}paragraph`, - `- content`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); - }); - - test('Smart select on second level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level 2`, - ` * level 1`, - ` * level ${CURSOR}1`, - `* level 0`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); - }); - - test('Smart select on third level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - ` * level 1`, - ` * level 1`, - `* level 0`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); - }); - - test('Smart select level 2 then level 1', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - `* level 1`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); - }); - - test('Smart select bold', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here **new${CURSOR}item** and here` - )); - assertNestedRangesEqual(ranges![0], [0, 13, 0, 30], [0, 11, 0, 32], [0, 0, 0, 41]); - }); - - test('Smart select link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here [text](https${CURSOR}://google.com) and here` - )); - assertNestedRangesEqual(ranges![0], [0, 18, 0, 46], [0, 17, 0, 47], [0, 11, 0, 47], [0, 0, 0, 56]); - }); - - test('Smart select brackets', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here [te${CURSOR}xt](https://google.com) and here` - )); - assertNestedRangesEqual(ranges![0], [0, 12, 0, 26], [0, 11, 0, 27], [0, 11, 0, 47], [0, 0, 0, 56]); - }); - - test('Smart select brackets under header in list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [te${CURSOR}xt](https://google.com) and here`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 14, 6, 28], [6, 13, 6, 29], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select link under header in list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [text](${CURSOR}https://google.com) and here`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 20, 6, 48], [6, 19, 6, 49], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select bold within list where multiple bold elements exists', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [text] **${CURSOR}items in here** and **here**`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 22, 6, 45], [6, 20, 6, 47], [6, 0, 6, 60], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select link in paragraph with multiple links', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `This[extension](https://marketplace.visualstudio.com/items?itemName=meganrogge.template-string-converter) addresses this [requ${CURSOR}est](https://github.com/microsoft/vscode/issues/56704) to convert Javascript/Typescript quotes to backticks when has been entered within a string.` - )); - assertNestedRangesEqual(ranges![0], [0, 123, 0, 140], [0, 122, 0, 141], [0, 122, 0, 191], [0, 0, 0, 283]); - }); - - test('Smart select bold link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `**[extens${CURSOR}ion](https://google.com)**` - )); - assertNestedRangesEqual(ranges![0], [0, 3, 0, 22], [0, 2, 0, 23], [0, 2, 0, 43], [0, 2, 0, 43], [0, 0, 0, 45], [0, 0, 0, 45]); - }); - - test('Smart select inline code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `[\`code ${CURSOR} link\`]` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 0, 0, 24]); - }); - - test('Smart select link with inline code block text', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `[\`code ${CURSOR} link\`](http://example.com)` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 1, 0, 23], [0, 0, 0, 24], [0, 0, 0, 44], [0, 0, 0, 44]); - }); - - test('Smart select italic', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*some nice ${CURSOR}text*` - )); - assertNestedRangesEqual(ranges![0], [0, 1, 0, 25], [0, 0, 0, 26], [0, 0, 0, 26]); - }); - - test('Smart select italic link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*[extens${CURSOR}ion](https://google.com)*` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 21], [0, 1, 0, 22], [0, 1, 0, 42], [0, 1, 0, 42], [0, 0, 0, 43], [0, 0, 0, 43]); - }); - - test('Smart select italic on end', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*word1 word2 word3${CURSOR}*` - )); - assertNestedRangesEqual(ranges![0], [0, 1, 0, 28], [0, 0, 0, 29], [0, 0, 0, 29]); - }); - - test('Smart select italic then bold', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `outer text **bold words *italic ${CURSOR} words* bold words** outer text` - )); - assertNestedRangesEqual(ranges![0], [0, 25, 0, 48], [0, 24, 0, 49], [0, 13, 0, 60], [0, 11, 0, 62], [0, 0, 0, 73]); - }); - - test('Smart select bold then italic', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `outer text *italic words **bold ${CURSOR} words** italic words* outer text` - )); - assertNestedRangesEqual(ranges![0], [0, 27, 0, 48], [0, 25, 0, 50], [0, 12, 0, 63], [0, 11, 0, 64], [0, 0, 0, 75]); - }); - - test('Third level header from release notes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `---`, - `Order: 60`, - `TOCTitle: October 2020`, - `PageTitle: Visual Studio Code October 2020`, - `MetaDescription: Learn what is new in the Visual Studio Code October 2020 Release (1.51)`, - `MetaSocialImage: 1_51/release-highlights.png`, - `Date: 2020-11-6`, - `DownloadVersion: 1.51.1`, - `---`, - `# October 2020 (version 1.51)`, - ``, - `**Update 1.51.1**: The update addresses these [issues](https://github.com/microsoft/vscode/issues?q=is%3Aissue+milestone%3A%22October+2020+Recovery%22+is%3Aclosed+).`, - ``, - ``, - ``, - `---`, - ``, - `Welcome to the October 2020 release of Visual Studio Code. As announced in the [October iteration plan](https://github.com/microsoft/vscode/issues/108473), we focused on housekeeping GitHub issues and pull requests as documented in our issue grooming guide.`, - ``, - `We also worked with our partners at GitHub on GitHub Codespaces, which ended up being more involved than originally anticipated. To that end, we'll continue working on housekeeping for part of the November iteration.`, - ``, - `During this housekeeping milestone, we also addressed several feature requests and community [pull requests](#thank-you). Read on to learn about new features and settings.`, - ``, - `## Workbench`, - ``, - `### More prominent pinned tabs`, - ``, - `${CURSOR}Pinned tabs will now always show their pin icon, even while inactive, to make them easier to identify. If an editor is both pinned and contains unsaved changes, the icon reflects both states.`, - ``, - `![Inactive pinned tabs showing pin icons](images/1_51/pinned-tabs.png)` - ) - ); - assertNestedRangesEqual(ranges![0], [27, 0, 27, 201], [26, 0, 29, 70], [25, 0, 29, 70], [24, 0, 29, 70], [23, 0, 29, 70], [10, 0, 29, 70], [9, 0, 29, 70]); - }); - -}); - - -function assertNestedLineNumbersEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) { - const lineage = getLineage(range); - assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); - for (let i = 0; i < lineage.length; i++) { - assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`); - } -} - -function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number, number, number][]) { - const lineage = getLineage(range); - assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); - for (let i = 0; i < lineage.length; i++) { - assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][2], `parent at a depth of ${i}`); - assert(lineage[i].range.start.character === expectedRanges[i][1], `parent at a depth of ${i} on start char`); - assert(lineage[i].range.end.character === expectedRanges[i][3], `parent at a depth of ${i} on end char`); - } -} - -function getLineage(range: vscode.SelectionRange): vscode.SelectionRange[] { - const result: vscode.SelectionRange[] = []; - let currentRange: vscode.SelectionRange | undefined = range; - while (currentRange) { - result.push(currentRange); - currentRange = currentRange.parent; - } - return result; -} - -function getValues(ranges: vscode.SelectionRange[]): string[] { - return ranges.map(range => { - return range.range.start.line + ' ' + range.range.start.character + ' ' + range.range.end.line + ' ' + range.range.end.character; - }); -} - -function assertLineNumbersEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) { - assert.strictEqual(selectionRange.range.start.line, startLine, `failed on start line ${message}`); - assert.strictEqual(selectionRange.range.end.line, endLine, `failed on end line ${message}`); -} - -function getSelectionRangesForDocument(contents: string, pos?: vscode.Position[]): Promise { - const doc = new InMemoryDocument(testFileName, contents); - const workspace = new InMemoryMdWorkspace([doc]); - const engine = createNewMarkdownEngine(); - const provider = new MdSmartSelect(engine, new MdTableOfContentsProvider(engine, workspace, nulLogger)); - const positions = pos ? pos : getCursorPositions(contents, doc); - return provider.provideSelectionRanges(doc, positions, new vscode.CancellationTokenSource().token); -} diff --git a/extensions/markdown-language-features/src/test/util.ts b/extensions/markdown-language-features/src/test/util.ts index 99369ae091f..b9dc2241090 100644 --- a/extensions/markdown-language-features/src/test/util.ts +++ b/extensions/markdown-language-features/src/test/util.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as os from 'os'; import * as vscode from 'vscode'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; export const joinLines = (...args: string[]) => @@ -37,3 +38,14 @@ export function assertRangeEqual(expected: vscode.Range, actual: vscode.Range, m assert.strictEqual(expected.end.line, actual.end.line, message); assert.strictEqual(expected.end.character, actual.end.character, message); } + +export function withStore(fn: (this: Mocha.Context, store: DisposableStore) => Promise) { + return async function (this: Mocha.Context): Promise { + const store = new DisposableStore(); + try { + return await fn.call(this, store); + } finally { + store.dispose(); + } + }; +} diff --git a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts index 55a57658d24..3762a5a4ba1 100644 --- a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts +++ b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts @@ -10,37 +10,40 @@ import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbols'; import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbols'; import { MdTableOfContentsProvider } from '../tableOfContents'; import { ITextDocument } from '../types/textDocument'; +import { DisposableStore } from '../util/dispose'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { IMdWorkspace } from '../workspace'; import { createNewMarkdownEngine } from './engine'; import { InMemoryMdWorkspace } from './inMemoryWorkspace'; import { nulLogger } from './nulLogging'; -import { workspacePath } from './util'; +import { withStore, workspacePath } from './util'; -function getWorkspaceSymbols(workspace: IMdWorkspace, query = ''): Promise { +function getWorkspaceSymbols(store: DisposableStore, workspace: IMdWorkspace, query = ''): Promise { const engine = createNewMarkdownEngine(); - const symbolProvider = new MdDocumentSymbolProvider(new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger); - return new MdWorkspaceSymbolProvider(symbolProvider, workspace).provideWorkspaceSymbols(query); + const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger)); + const symbolProvider = new MdDocumentSymbolProvider(tocProvider, nulLogger); + const workspaceSymbolProvider = store.add(new MdWorkspaceSymbolProvider(symbolProvider, workspace)); + return workspaceSymbolProvider.provideWorkspaceSymbols(query); } suite('markdown.WorkspaceSymbolProvider', () => { - test('Should not return anything for empty workspace', async () => { - const workspace = new InMemoryMdWorkspace([]); - assert.deepStrictEqual(await getWorkspaceSymbols(workspace, ''), []); - }); + test('Should not return anything for empty workspace', withStore(async (store) => { + const workspace = store.add(new InMemoryMdWorkspace([])); + assert.deepStrictEqual(await getWorkspaceSymbols(store, workspace, ''), []); + })); - test('Should return symbols from workspace with one markdown file', async () => { - const workspace = new InMemoryMdWorkspace([ + test('Should return symbols from workspace with one markdown file', withStore(async (store) => { + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(workspacePath('test.md'), `# header1\nabc\n## header2`) - ]); + ])); - const symbols = await getWorkspaceSymbols(workspace, ''); + const symbols = await getWorkspaceSymbols(store, workspace, ''); assert.strictEqual(symbols.length, 2); assert.strictEqual(symbols[0].name, '# header1'); assert.strictEqual(symbols[1].name, '## header2'); - }); + })); - test('Should return all content basic workspace', async () => { + test('Should return all content basic workspace', withStore(async (store) => { const fileNameCount = 10; const files: ITextDocument[] = []; for (let i = 0; i < fileNameCount; ++i) { @@ -48,55 +51,55 @@ suite('markdown.WorkspaceSymbolProvider', () => { files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`)); } - const workspace = new InMemoryMdWorkspace(files); + const workspace = store.add(new InMemoryMdWorkspace(files)); - const symbols = await getWorkspaceSymbols(workspace, ''); + const symbols = await getWorkspaceSymbols(store, workspace, ''); assert.strictEqual(symbols.length, fileNameCount * 2); - }); + })); - test('Should update results when markdown file changes symbols', async () => { + test('Should update results when markdown file changes symbols', withStore(async (store) => { const testFileName = workspacePath('test.md'); - const workspace = new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(testFileName, `# header1`, 1 /* version */) - ]); + ])); - assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1); + assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1); // Update file workspace.updateDocument(new InMemoryDocument(testFileName, `# new header\nabc\n## header2`, 2 /* version */)); - const newSymbols = await getWorkspaceSymbols(workspace, ''); + const newSymbols = await getWorkspaceSymbols(store, workspace, ''); assert.strictEqual(newSymbols.length, 2); assert.strictEqual(newSymbols[0].name, '# new header'); assert.strictEqual(newSymbols[1].name, '## header2'); - }); + })); - test('Should remove results when file is deleted', async () => { + test('Should remove results when file is deleted', withStore(async (store) => { const testFileName = workspacePath('test.md'); - const workspace = new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(testFileName, `# header1`) - ]); + ])); - assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1); + assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1); // delete file workspace.deleteDocument(testFileName); - const newSymbols = await getWorkspaceSymbols(workspace, ''); + const newSymbols = await getWorkspaceSymbols(store, workspace, ''); assert.strictEqual(newSymbols.length, 0); - }); + })); - test('Should update results when markdown file is created', async () => { + test('Should update results when markdown file is created', withStore(async (store) => { const testFileName = workspacePath('test.md'); - const workspace = new InMemoryMdWorkspace([ + const workspace = store.add(new InMemoryMdWorkspace([ new InMemoryDocument(testFileName, `# header1`) - ]); + ])); - assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1); + assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1); // Create file workspace.createDocument(new InMemoryDocument(workspacePath('test2.md'), `# new header\nabc\n## header2`)); - const newSymbols = await getWorkspaceSymbols(workspace, ''); + const newSymbols = await getWorkspaceSymbols(store, workspace, ''); assert.strictEqual(newSymbols.length, 3); - }); + })); }); diff --git a/extensions/markdown-language-features/src/types/textDocument.ts b/extensions/markdown-language-features/src/types/textDocument.ts index 5c49e065d40..f45614cc0cc 100644 --- a/extensions/markdown-language-features/src/types/textDocument.ts +++ b/extensions/markdown-language-features/src/types/textDocument.ts @@ -3,15 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as vscode from 'vscode'; - -/** - * Minimal version of {@link vscode.TextLine}. - */ -export interface ITextLine { - readonly text: string; - readonly isEmptyOrWhitespace: boolean; -} +import * as vscode from 'vscode'; /** * Minimal version of {@link vscode.TextDocument}. @@ -22,6 +14,9 @@ export interface ITextDocument { readonly lineCount: number; getText(range?: vscode.Range): string; - lineAt(line: number): ITextLine; positionAt(offset: number): vscode.Position; } + +export function getLine(doc: ITextDocument, line: number): string { + return doc.getText(new vscode.Range(line, 0, line, Number.MAX_VALUE)).replace(/\r?\n$/, ''); +} diff --git a/extensions/markdown-language-features/src/util/dispose.ts b/extensions/markdown-language-features/src/util/dispose.ts index 175acf7b367..ca78e4a821b 100644 --- a/extensions/markdown-language-features/src/util/dispose.ts +++ b/extensions/markdown-language-features/src/util/dispose.ts @@ -5,13 +5,36 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: vscode.Disposable[]) { - while (disposables.length) { - const item = disposables.pop(); - item?.dispose(); +export class MultiDisposeError extends Error { + constructor( + public readonly errors: any[] + ) { + super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); } } +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + + for (const disposable of disposables) { + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new MultiDisposeError(errors); + } +} + +export interface IDisposable { + dispose(): void; +} + export abstract class Disposable { private _isDisposed = false; @@ -25,7 +48,7 @@ export abstract class Disposable { disposeAll(this._disposables); } - protected _register(value: T): T { + protected _register(value: T): T { if (this._isDisposed) { value.dispose(); } else { @@ -38,3 +61,22 @@ export abstract class Disposable { return this._isDisposed; } } + +export class DisposableStore extends Disposable { + private readonly items = new Set(); + + public override dispose() { + super.dispose(); + disposeAll(this.items); + this.items.clear(); + } + + public add(item: T): T { + if (this.isDisposed) { + console.warn('Adding to disposed store. Item will be leaked'); + } + + this.items.add(item); + return item; + } +} diff --git a/extensions/markdown-language-features/src/util/inMemoryDocument.ts b/extensions/markdown-language-features/src/util/inMemoryDocument.ts index de29de21355..f626878deff 100644 --- a/extensions/markdown-language-features/src/util/inMemoryDocument.ts +++ b/extensions/markdown-language-features/src/util/inMemoryDocument.ts @@ -5,14 +5,12 @@ import * as vscode from 'vscode'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { ITextDocument, ITextLine } from '../types/textDocument'; +import { ITextDocument } from '../types/textDocument'; export class InMemoryDocument implements ITextDocument { private readonly _doc: TextDocument; - private lines: ITextLine[] | undefined; - constructor( public readonly uri: vscode.Uri, contents: string, public readonly version = 0, @@ -25,16 +23,6 @@ export class InMemoryDocument implements ITextDocument { return this._doc.lineCount; } - lineAt(index: any): ITextLine { - if (!this.lines) { - this.lines = this._doc.getText().split(/\r?\n/).map(text => ({ - text, - get isEmptyOrWhitespace() { return /^\s*$/.test(text); } - })); - } - return this.lines[index]; - } - positionAt(offset: number): vscode.Position { const pos = this._doc.positionAt(offset); return new vscode.Position(pos.line, pos.character); diff --git a/extensions/markdown-language-features/src/util/schemes.ts b/extensions/markdown-language-features/src/util/schemes.ts index a90c71aef57..e18f60ed81d 100644 --- a/extensions/markdown-language-features/src/util/schemes.ts +++ b/extensions/markdown-language-features/src/util/schemes.ts @@ -5,21 +5,21 @@ import * as vscode from 'vscode'; -export const Schemes = { - http: 'http:', - https: 'https:', - file: 'file:', +export const Schemes = Object.freeze({ + http: 'http', + https: 'https', + file: 'file', untitled: 'untitled', - mailto: 'mailto:', - data: 'data:', - vscode: 'vscode:', - 'vscode-insiders': 'vscode-insiders:', + mailto: 'mailto', + data: 'data', + vscode: 'vscode', + 'vscode-insiders': 'vscode-insiders', notebookCell: 'vscode-notebook-cell', -}; +}); const knownSchemes = [ ...Object.values(Schemes), - `${vscode.env.uriScheme}:` + `${vscode.env.uriScheme}` ]; export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | undefined { @@ -31,5 +31,5 @@ export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | } export function isOfScheme(scheme: string, link: string): boolean { - return link.toLowerCase().startsWith(scheme); + return link.toLowerCase().startsWith(scheme + ':'); } diff --git a/extensions/markdown-language-features/src/util/string.ts b/extensions/markdown-language-features/src/util/string.ts new file mode 100644 index 00000000000..dd9733e9ffd --- /dev/null +++ b/extensions/markdown-language-features/src/util/string.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function isEmptyOrWhitespace(str: string): boolean { + return /^\s*$/.test(str); +} diff --git a/extensions/markdown-language-features/src/util/workspaceCache.ts b/extensions/markdown-language-features/src/util/workspaceCache.ts index 5e9e4010976..8432675c87a 100644 --- a/extensions/markdown-language-features/src/util/workspaceCache.ts +++ b/extensions/markdown-language-features/src/util/workspaceCache.ts @@ -143,6 +143,16 @@ export class MdWorkspaceInfoCache extends Disposable { return Array.from(await this._cache.entries(), x => x[1]); } + public async getForDocs(docs: readonly ITextDocument[]): Promise { + for (const doc of docs) { + if (!this._cache.has(doc.uri)) { + this.update(doc); + } + } + + return Promise.all(docs.map(doc => this._cache.get(doc.uri) as Promise)); + } + private async ensureInit(): Promise { if (!this._init) { this._init = this.populateCache(); @@ -157,7 +167,9 @@ export class MdWorkspaceInfoCache extends Disposable { private async populateCache(): Promise { const markdownDocumentUris = await this.workspace.getAllMarkdownDocuments(); for (const document of markdownDocumentUris) { - this.update(document); + if (!this._cache.has(document.uri)) { + this.update(document); + } } } diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 9052af7c6df..13eeca2f9da 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/dompurify@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.1.tgz#2934adcd31c4e6b02676f9c22f9756e5091c04dd" @@ -59,16 +95,37 @@ resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf" integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + dompurify@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" @@ -96,6 +153,13 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + markdown-it-front-matter@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.2.1.tgz#dca49a827bb3cebb0528452c1d87dff276eb28dc" @@ -117,6 +181,13 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + morphdom@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e" @@ -127,16 +198,50 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== + +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== + dependencies: + minimatch "^3.0.4" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" + +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== + dependencies: + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" + vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== + vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" @@ -146,3 +251,8 @@ vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/extensions/markdown-math/.gitignore b/extensions/markdown-math/.gitignore index 67c177886fb..93d9e664eae 100644 --- a/extensions/markdown-math/.gitignore +++ b/extensions/markdown-math/.gitignore @@ -1 +1,2 @@ notebook-out +languageService diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts index ee68b15d61b..299ab9373de 100644 --- a/extensions/markdown-math/notebook/katex.ts +++ b/extensions/markdown-math/notebook/katex.ts @@ -8,9 +8,9 @@ import type { RendererContext } from 'vscode-notebook-renderer'; const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css'); export async function activate(ctx: RendererContext) { - const markdownItRenderer = (await ctx.getRenderer('markdownItRenderer')) as undefined | any; + const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as undefined | any; if (!markdownItRenderer) { - throw new Error('Could not load markdownItRenderer'); + throw new Error(`Could not load 'vscode.markdown-it-renderer'`); } // Add katex styles to be copied to shadow dom diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index d3bb21a553a..045da771eb9 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -6,7 +6,7 @@ "icon": "icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.54.0" }, @@ -58,10 +58,10 @@ ], "notebookRenderer": [ { - "id": "markdownItRenderer-katex", + "id": "vscode.markdown-it-katex-extension", "displayName": "Markdown it KaTeX renderer", "entrypoint": { - "extends": "markdownItRenderer", + "extends": "vscode.markdown-it-renderer", "path": "./notebook-out/katex.js" } } diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index 05603e468b2..efc984ed5b9 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -6,7 +6,7 @@ "icon": "media/icon.png", "version": "1.0.0", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.5.0" }, diff --git a/extensions/merge-conflict/src/services.ts b/extensions/merge-conflict/src/services.ts index 1b0f875aace..932a665f4bc 100644 --- a/extensions/merge-conflict/src/services.ts +++ b/extensions/merge-conflict/src/services.ts @@ -53,7 +53,7 @@ export default class ServiceWrapper implements vscode.Disposable { // using the merge editor we disable this extension - for the merge editor but also // for "other" editors const gitConfig = vscode.workspace.getConfiguration('git'); - if (gitConfig.get('experimental.mergeEditor')) { + if (gitConfig.get('mergeEditor')) { return { enableCodeLens: false, enableDecorations: false, diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index f206ce9e5a5..f8b8a6931d0 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -36,7 +36,7 @@ } ] }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", "scripts": { @@ -60,7 +60,7 @@ "sha.js": "2.4.11", "stream": "0.0.2", "uuid": "^8.2.0", - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "vscode-nls": "^5.0.0" }, "repository": { diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index dea1d2e471a..9328b1d4b1a 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -39,10 +75,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" asynckit@^0.4.0: version "0.4.0" diff --git a/extensions/npm/src/npmScriptLens.ts b/extensions/npm/src/npmScriptLens.ts index 067209da334..2834d3e7639 100644 --- a/extensions/npm/src/npmScriptLens.ts +++ b/extensions/npm/src/npmScriptLens.ts @@ -71,7 +71,7 @@ export class NpmScriptLensProvider implements CodeLensProvider, Disposable { return []; } - const title = localize('codelens.debug', '{0} Debug', '$(debug-start)'); + const title = '$(debug-start) ' + localize('codelens.debug', 'Debug'); const cwd = path.dirname(document.uri.fsPath); if (this.lensLocation === 'top') { return [ diff --git a/extensions/references-view/src/references-view.d.ts b/extensions/references-view/src/references-view.d.ts index 70fd932a19f..9ec370029a7 100644 --- a/extensions/references-view/src/references-view.d.ts +++ b/extensions/references-view/src/references-view.d.ts @@ -6,14 +6,14 @@ import * as vscode from 'vscode'; /** - * This interface describes the shape for the references viewlet API. It consists - * of a single `setInput` function which must be called with a full implementation + * This interface describes the shape for the references viewlet API. It consists + * of a single `setInput` function which must be called with a full implementation * of the `SymbolTreeInput`-interface. To acquire this API use the default mechanics, e.g: - * + * * ```ts * // get references viewlet API - * const api = await vscode.extensions.getExtension('ms-vscode.references-view').activate(); - * + * const api = await vscode.extensions.getExtension('vscode.references-view').activate(); + * * // instantiate and set input which updates the view * const myInput: SymbolTreeInput = ... * api.setInput(myInput) @@ -22,8 +22,8 @@ import * as vscode from 'vscode'; export interface SymbolTree { /** - * Set the contents of the references viewlet. - * + * Set the contents of the references viewlet. + * * @param input A symbol tree input object */ setInput(input: SymbolTreeInput): void; @@ -31,7 +31,7 @@ export interface SymbolTree { /** * A symbol tree input is the entry point for populating the references viewlet. - * Inputs must be anchored at a code location, they must have a title, and they + * Inputs must be anchored at a code location, they must have a title, and they * must resolve to a model. */ export interface SymbolTreeInput { @@ -54,17 +54,17 @@ export interface SymbolTreeInput { readonly location: vscode.Location; /** - * Resolve this input to a model that contains the actual data. When there are no result + * Resolve this input to a model that contains the actual data. When there are no result * than `undefined` or `null` should be returned. */ resolve(): vscode.ProviderResult>; /** * This function is called when re-running from history. The symbols tree has tracked - * the original location of this input and that is now passed to this input. The + * the original location of this input and that is now passed to this input. The * implementation of this function should return a clone where the `location`-property * uses the provided `location` - * + * * @param location The location at which the new input should be anchored. * @returns A new input which location is anchored at the position. */ @@ -94,7 +94,7 @@ export interface SymbolTreeModel { navigation?: SymbolItemNavigation; /** - * Optional support for editor highlights. WHen implemented, the editor will highlight + * Optional support for editor highlights. WHen implemented, the editor will highlight * symbol ranges in the source code. */ highlights?: SymbolItemEditorHighlights; diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 7ba03888339..08344e234f1 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -9,7 +9,7 @@ "icon": "media/icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.53.0" }, @@ -67,7 +67,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "vscode-nls": "^5.0.0" }, "devDependencies": { diff --git a/extensions/simple-browser/yarn.lock b/extensions/simple-browser/yarn.lock index 0a897c7ddba..5faf627618c 100644 --- a/extensions/simple-browser/yarn.lock +++ b/extensions/simple-browser/yarn.lock @@ -2,15 +2,54 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/vscode-webview@^1.57.0": version "1.57.0" resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf" integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" vscode-codicons@^0.0.14: version "0.0.14" diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index f1d6e04e5ad..b40cb39a6e3 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "microsoft/vscode-mssql", "repositoryUrl": "https://github.com/microsoft/vscode-mssql", - "commitHash": "c98518dd7418ddfb6f35676e14cf12791b0a235d" + "commitHash": "b8b58864526c048002b7c3964bdac8aac3713bd9" } }, "license": "MIT", - "version": "1.10.1" + "version": "1.16.0" } ], "version": 1 diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index ec0e461d9f8..26575ff4549 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-mssql/commit/c98518dd7418ddfb6f35676e14cf12791b0a235d", + "version": "https://github.com/microsoft/vscode-mssql/commit/b8b58864526c048002b7c3964bdac8aac3713bd9", "name": "SQL", "scopeName": "source.sql", "patterns": [ @@ -191,7 +191,7 @@ "name": "keyword.operator.concatenator.sql" }, { - "match": "(?i)\\b(avg|checksum_agg|count|count_big|grouping|grouping_id|max|min|sum|stdev|stdevp|var|varp)\\b", + "match": "(?i)\\b(aggregate|approx_count_distinct|avg|checksum_agg|count|count_big|grouping|grouping_id|max|min|sum|stdev|stdevp|var|varp)\\b", "name": "support.function.aggregate.sql" }, { @@ -202,14 +202,26 @@ "match": "(?i)\\b(cast|convert|parse|try_cast|try_convert|try_parse)\\b", "name": "support.function.conversion.sql" }, + { + "match": "(?i)\\b(collationproperty|tertiary_weights)\\b", + "name": "support.function.collation.sql" + }, + { + "match": "(?i)\\b(asymkey_id|asymkeyproperty|certproperty|cert_id|crypt_gen_random|decryptbyasymkey|decryptbycert|decryptbykey|decryptbykeyautoasymkey|decryptbykeyautocert|decryptbypassphrase|encryptbyasymkey|encryptbycert|encryptbykey|encryptbypassphrase|hashbytes|is_objectsigned|key_guid|key_id|key_name|signbyasymkey|signbycert|symkeyproperty|verifysignedbycert|verifysignedbyasymkey)\\b", + "name": "support.function.cryptographic.sql" + }, { "match": "(?i)\\b(cursor_status)\\b", "name": "support.function.cursor.sql" }, { - "match": "(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|eomonth|switchoffset|todatetimeoffset|isdate)\\b", + "match": "(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|eomonth|switchoffset|todatetimeoffset|isdate|date_bucket)\\b", "name": "support.function.datetime.sql" }, + { + "match": "(?i)\\b(datalength|ident_current|ident_incr|ident_seed|identity|sql_variant_property)\\b", + "name": "support.function.datatype.sql" + }, { "match": "(?i)\\b(coalesce|nullif)\\b", "name": "support.function.expression.sql" @@ -219,7 +231,11 @@ "name": "support.function.globalvar.sql" }, { - "match": "(?i)\\b(choose|iif)\\b", + "match": "(?i)\\b(json|isjson|json_object|json_array|json_value|json_query|json_modify|json_path_exists)\\b", + "name": "support.function.json.sql" + }, + { + "match": "(?i)\\b(choose|iif|greatest|least)\\b", "name": "support.function.logical.sql" }, { @@ -235,7 +251,7 @@ "name": "support.function.ranking.sql" }, { - "match": "(?i)\\b(opendatasource|openrowset|openquery|openxml)\\b", + "match": "(?i)\\b(generate_series|opendatasource|openjson|openrowset|openquery|openxml|predict|string_split)\\b", "name": "support.function.rowset.sql" }, { diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 8bcfb2d2f40..983ec9c5157 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "6b83574de165123583d6d8d5b3b6c91f04b7153d" + "commitHash": "4dd6c27e1f5aed8068c2451dbaf0db3364545937" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 70e9074c0fb..24cde729664 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 6f7f20bd8c3..0914afe46c1 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1881,6 +1881,7 @@ "dockerfile": "_docker", "ignore": "_git", "fsharp": "_f-sharp", + "git-commit": "_git", "go": "_go2", "groovy": "_grails", "handlebars": "_mustache", @@ -2197,6 +2198,7 @@ "dockerfile": "_docker_light", "ignore": "_git_light", "fsharp": "_f-sharp_light", + "git-commit": "_git_light", "go": "_go2_light", "groovy": "_grails_light", "handlebars": "_mustache_light", @@ -2338,5 +2340,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/6b83574de165123583d6d8d5b3b6c91f04b7153d" + "version": "https://github.com/jesseweed/seti-ui/commit/4dd6c27e1f5aed8068c2451dbaf0db3364545937" } \ No newline at end of file diff --git a/extensions/typescript-basics/language-configuration.json b/extensions/typescript-basics/language-configuration.json index 07260718b64..739f085339e 100644 --- a/extensions/typescript-basics/language-configuration.json +++ b/extensions/typescript-basics/language-configuration.json @@ -99,6 +99,24 @@ ">" ] ], + "colorizedBracketPairs": [ + [ + "(", + ")" + ], + [ + "[", + "]" + ], + [ + "{", + "}" + ], + [ + "<", + ">" + ] + ], "autoCloseBefore": ";:.,=}])>` \n\t", "folding": { "markers": { diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index cb6c20d8eed..fef8f0221f0 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -64,6 +64,13 @@ "language": "typescript", "scopeName": "source.ts", "path": "./syntaxes/TypeScript.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.operator.relational", + "storage.type.function.arrow", + "keyword.operator.bitwise.shift", + "meta.brace.angle", + "punctuation.definition.tag" + ], "tokenTypes": { "meta.template.expression": "other", "meta.template.expression string": "string", @@ -78,6 +85,12 @@ "language": "typescriptreact", "scopeName": "source.tsx", "path": "./syntaxes/TypeScriptReact.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.operator.relational", + "storage.type.function.arrow", + "keyword.operator.bitwise.shift", + "punctuation.definition.tag" + ], "embeddedLanguages": { "meta.tag.tsx": "jsx-tags", "meta.tag.without-attributes.tsx": "jsx-tags", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index bf441e4a180..be5a8bacb28 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -6,7 +6,7 @@ "author": "vscode", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ "resolvers", "workspaceTrust" @@ -34,10 +34,11 @@ "Programming Languages" ], "dependencies": { - "@vscode/extension-telemetry": "0.6.1", + "@vscode/extension-telemetry": "0.6.2", "jsonc-parser": "^2.2.1", "semver": "5.5.1", "vscode-nls": "^5.0.0", + "vscode-tas-client": "^0.1.47", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -982,6 +983,22 @@ "markdownDescription": "%typescript.preferences.includePackageJsonAutoImports%", "scope": "window" }, + "typescript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, + "javascript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, "javascript.preferences.renameShorthandProperties": { "type": "boolean", "default": true, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 513bcfbaaa1..03752b1f095 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -136,6 +136,7 @@ "typescript.preferences.includePackageJsonAutoImports.auto": "Search dependencies based on estimated performance impact.", "typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.", "typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.", + "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Requires using TypeScript 4.8 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts new file mode 100644 index 00000000000..3b24c612c9a --- /dev/null +++ b/extensions/typescript-language-features/src/experimentationService.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * 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 VsCodeTelemetryReporter from '@vscode/extension-telemetry'; +import * as tas from 'vscode-tas-client'; + +interface ExperimentTypes { + // None for now. +} + +export class ExperimentationService implements vscode.Disposable { + private _experimentationServicePromise: Promise; + private _telemetryReporter: ExperimentTelemetryReporter; + + constructor(private readonly _extensionContext: vscode.ExtensionContext) { + this._telemetryReporter = new ExperimentTelemetryReporter(_extensionContext); + this._experimentationServicePromise = this.createExperimentationService(); + } + + public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { + const experimentationService = await this._experimentationServicePromise; + try { + const treatmentVariable = experimentationService.getTreatmentVariableAsync('vscode', name, /*checkCache*/ true) as ExperimentTypes[K]; + return treatmentVariable; + } catch { + return defaultValue; + } + } + + private async createExperimentationService(): Promise { + let targetPopulation: tas.TargetPopulation; + switch (vscode.env.uriScheme) { + case 'vscode': + targetPopulation = tas.TargetPopulation.Public; + case 'vscode-insiders': + targetPopulation = tas.TargetPopulation.Insiders; + case 'vscode-exploration': + targetPopulation = tas.TargetPopulation.Internal; + case 'code-oss': + targetPopulation = tas.TargetPopulation.Team; + default: + targetPopulation = tas.TargetPopulation.Public; + } + + const id = this._extensionContext.extension.id; + const version = this._extensionContext.extension.packageJSON.version || ''; + const experimentationService = tas.getExperimentationService(id, version, targetPopulation, this._telemetryReporter, this._extensionContext.globalState); + await experimentationService.initialFetch; + return experimentationService; + } + + + /** + * @inheritdoc + */ + public dispose() { + this._telemetryReporter.dispose(); + } +} + +export class ExperimentTelemetryReporter + implements tas.IExperimentationTelemetry, vscode.Disposable { + private _sharedProperties: Record = {}; + private _reporter: VsCodeTelemetryReporter; + constructor(ctxt: vscode.ExtensionContext) { + const extension = ctxt.extension; + const packageJSON = extension.packageJSON; + this._reporter = new VsCodeTelemetryReporter( + extension.id, + packageJSON.version || '', + packageJSON.aiKey || ''); + + } + + setSharedProperty(name: string, value: string): void { + this._sharedProperties[name] = value; + } + + postEvent(eventName: string, props: Map): void { + const propsObject = { + ...this._sharedProperties, + ...Object.fromEntries(props), + }; + this._reporter.sendTelemetryEvent(eventName, propsObject); + } + + dispose() { + this._reporter.dispose(); + } +} diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index bc0ac10388b..caeb91d1dc3 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; @@ -189,6 +190,8 @@ export default class FileConfigurationManager extends Disposable { includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true), includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), + // @ts-expect-error until TS 4.8 + autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, @@ -205,6 +208,18 @@ export default class FileConfigurationManager extends Disposable { default: return this.client.apiVersion.gte(API.v333) ? 'auto' : undefined; } } + + private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined { + return workspaceFolder && config.get('autoImportFileExcludePatterns')?.map(p => { + // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578 + const slashNormalized = p.replace(/\\/g, '/'); + const isRelative = /^\.\.?($|\/)/.test(slashNormalized); + return path.isAbsolute(p) ? p : + p.startsWith('*') ? '/' + slashNormalized : + isRelative ? vscode.Uri.joinPath(workspaceFolder, p).fsPath : + '/**/' + slashNormalized; + }); + } } export class InlayHintSettingNames { diff --git a/extensions/typescript-language-features/src/languageFeatures/fixAll.ts b/extensions/typescript-language-features/src/languageFeatures/fixAll.ts index ad8630f70e7..9119b5b66ba 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fixAll.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fixAll.ts @@ -133,7 +133,7 @@ class SourceFixAll extends SourceAction { static readonly kind = vscode.CodeActionKind.SourceFixAll.append('ts'); constructor() { - super(localize('autoFix.label', 'Fix All'), SourceFixAll.kind); + super(localize('autoFix.label', 'Fix all fixable JS/TS issues'), SourceFixAll.kind); } async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index ccf832aca13..c400b672129 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" @@ -12,10 +48,25 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== -"@vscode/extension-telemetry@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.1.tgz#f8d1f7145baf932b75077c48107edff48501fc14" - integrity sha512-Y4Oc8yGURGVF4WhCZcu+EVy+MAIeQDLDVeDlLn59H0C1w+7xr8dL2ZtDBioy+Hog1Edrd6zOwr3Na7xe1iC/UA== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" + +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + +follow-redirects@^1.14.8: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== jsonc-parser@^2.2.1: version "2.3.1" @@ -27,11 +78,25 @@ semver@5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== +tas-client@0.1.45: + version "0.1.45" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.45.tgz#83bbf73f8458a0f527f9a389f7e1c37f63a64a76" + integrity sha512-IG9UmCpDbDPK23UByQ27rLybkRZYEx2eC9EkieXdwPKKjZPD2zPwfQmyGnZrZet4FUt3yj0ytkwz+liR9Nz/nA== + dependencies: + axios "^0.26.1" + vscode-nls@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-tas-client@^0.1.47: + version "0.1.47" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.47.tgz#d66795cbbaa231aba659b6c40d43927d73596375" + integrity sha512-SlEPDi+0gwxor4ANzBtXwqROPQdQkClHeVJgnkvdDF5Xnl407htCsabTPAq4Di8muObORtLchqQS/k1ocaGDEg== + dependencies: + tas-client "0.1.45" + vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 610dd2590e7..b7ae5707c53 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -33,6 +33,7 @@ "scmActionButton", "scmSelectedProvider", "scmValidation", + "snippetWorkspaceEdit", "taskPresentationGroup", "terminalDataWriteEvent", "terminalDimensions", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 708ed02f709..0f85b437235 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -33,9 +33,9 @@ async function addCell(code: string, notebook: vscode.NotebookDocument) { return notebook.cellAt(notebook.cellCount - 1); } -async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { +async function addCellAndRun(code: string, notebook: vscode.NotebookDocument, i: number) { const cell = await addCell(code, notebook); - await vscode.commands.executeCommand('notebook.execute'); + await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }); assert.strictEqual(cell.outputs.length, 1, 'execute failed'); return cell; } @@ -78,7 +78,7 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { // Run and add a bunch of cells for (let i = 0; i < 10; i++) { - await addCellAndRun(`print ${i}`, notebookEditor.notebook); + await addCellAndRun(`print ${i}`, notebookEditor.notebook, i); } // Verify visible range has the last cell diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index a055cf45520..d09fe8fe5c5 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -286,9 +286,6 @@ suite('vscode API - workspace', () => { sub.dispose(); }); - function assertEqualPath(a: string, b: string): void { - assert.ok(pathEquals(a, b), `${a} <-> ${b}`); - } test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => { const file = await createRandomFile(); @@ -296,23 +293,20 @@ suite('vscode API - workspace', () => { await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - const pendingAsserts: Function[] = []; - let onDidOpenTextDocument = false; + const onDidOpenTextDocument = new Set(); + const onDidChangeTextDocument = new Set(); + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidOpenTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidOpenTextDocument = true; + onDidOpenTextDocument.add(e); })); - let onDidChangeTextDocument = false; disposables.push(vscode.workspace.onDidChangeTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath)); - onDidChangeTextDocument = true; + onDidChangeTextDocument.add(e.document); })); - let onDidSaveTextDocument = false; disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -323,10 +317,10 @@ suite('vscode API - workspace', () => { }); await doc.save(); - assert.ok(onDidOpenTextDocument); - assert.ok(onDidChangeTextDocument); - assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(assert => assert()); + assert.ok(Array.from(onDidOpenTextDocument).find(e => e.uri.toString() === file.toString()), 'did Open: ' + file.toString()); + assert.ok(Array.from(onDidChangeTextDocument).find(e => e.uri.toString() === file.toString()), 'did Change: ' + file.toString()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); + disposeAll(disposables); return deleteFile(file); }); @@ -334,14 +328,13 @@ suite('vscode API - workspace', () => { test('events: onDidSaveTextDocument fires even for non dirty file when saved', async () => { const file = await createRandomFile(); const disposables: vscode.Disposable[] = []; - const pendingAsserts: Function[] = []; await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - let onDidSaveTextDocument = false; + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -349,7 +342,7 @@ suite('vscode API - workspace', () => { await vscode.commands.executeCommand('workbench.action.files.save'); assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(fn => fn()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); disposeAll(disposables); return deleteFile(file); }); @@ -1154,4 +1147,24 @@ suite('vscode API - workspace', () => { assert.strictEqual(document.isDirty, false); } }); + + test('SnippetString in WorkspaceEdit', async function (): Promise { + const file = await createRandomFile('hello\nworld'); + + const document = await vscode.workspace.openTextDocument(file); + const edt = await vscode.window.showTextDocument(document); + + assert.ok(edt === vscode.window.activeTextEditor); + + const we = new vscode.WorkspaceEdit(); + we.replace(document.uri, new vscode.Range(0, 0, 0, 0), new vscode.SnippetString('${1:foo}${2:bar}')); + const success = await vscode.workspace.applyEdit(we); + if (edt !== vscode.window.activeTextEditor) { + return this.skip(); + } + + assert.ok(success); + assert.strictEqual(document.getText(), 'foobarhello\nworld'); + assert.deepStrictEqual(edt.selections, [new vscode.Selection(0, 0, 0, 3)]); + }); }); diff --git a/package.json b/package.json index ebb9a26db70..592494a447a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.69.0", - "distro": "dfc2c2079a7e1bb5560221e84476116d10153875", + "version": "1.70.0", + "distro": "954078b4ad7e8d2b00615cfda5d89e5de196f696", "author": { "name": "Microsoft Corporation" }, @@ -61,14 +61,12 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", "@parcel/watcher": "2.0.5", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/ripgrep": "^1.14.2", "@vscode/sqlite3": "5.0.8", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.4.2", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", @@ -87,19 +85,18 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.60", - "xterm-addon-search": "0.9.0-beta.39", - "xterm-addon-serialize": "0.7.0-beta.13", + "xterm": "4.20.0-beta.5", + "xterm-addon-search": "0.10.0-beta.1", + "xterm-addon-serialize": "0.8.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.41", - "xterm-headless": "4.19.0-beta.60", + "xterm-addon-webgl": "0.13.0-beta.2", + "xterm-headless": "4.20.0-beta.5", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "devDependencies": { "7zip": "0.0.6", "@playwright/test": "1.21.0", - "@types/applicationinsights": "0.20.0", "@types/cookie": "^0.3.3", "@types/copy-webpack-plugin": "^6.0.3", "@types/cssnano": "^4.0.0", @@ -202,7 +199,7 @@ "style-loader": "^1.3.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220614", + "typescript": "^4.8.0-dev.20220706", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/product.json b/product.json index d16abf8de29..a50c00f6cf3 100644 --- a/product.json +++ b/product.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.68.0", + "version": "1.69.0", "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 e6fdc7a871b..8da57bba60b 100644 --- a/remote/package.json +++ b/remote/package.json @@ -5,12 +5,10 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", "@parcel/watcher": "2.0.5", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/ripgrep": "^1.14.2", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.4.2", "cookie": "^0.4.0", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", @@ -26,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.60", - "xterm-addon-search": "0.9.0-beta.39", - "xterm-addon-serialize": "0.7.0-beta.13", + "xterm": "4.20.0-beta.5", + "xterm-addon-search": "0.10.0-beta.1", + "xterm-addon-serialize": "0.8.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.41", - "xterm-headless": "4.19.0-beta.60", + "xterm-addon-webgl": "0.13.0-beta.2", + "xterm-headless": "4.20.0-beta.5", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index fcc462b5733..c3a32f4d4ec 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,16 +5,15 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", "jschardet": "3.0.0", "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.60", - "xterm-addon-search": "0.9.0-beta.39", + "xterm": "4.20.0-beta.5", + "xterm-addon-search": "0.10.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.41" + "xterm-addon-webgl": "0.13.0-beta.2" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 643265cb38c..75a41e50c82 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -20,35 +20,6 @@ "@microsoft/applicationinsights-shims" "^2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-analytics-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.8.4.tgz#3b32d8a2122be5d5993c74ef3217ebbf4876ea69" - integrity sha512-n/FPs8SS6rB8h+u157fiRh0TwUWKctxGNvr4M+LKeSdgDvf9c759gUeMR7r8xF6kBBfgkbmyaVORjsA1WJsU4g== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-channel-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.4.tgz#6de7210d87e61c72d3a9a06cbaeae14e1b543484" - integrity sha512-aml49Jya8LxX4tvyBbIvcxSo7UGI0k3HeiJQRFLeO+QlA+Ocsl10PqphU/OYJ4hh/P5/2QhEAq5bBM/b9/PNrg== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-common@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.4.tgz#45b422cf1804df06d5abb2ceda5ed65268a92135" - integrity sha512-uDvd4zxNGNYFE0TF4h7tAg+eMIPatyd1QdkP8fA4UYwshF4/+UwS1wegjXLEWQRRH87+UAyvx4IKQjobzzEX0A== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-core-js@2.8.4": version "2.8.4" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" @@ -57,45 +28,11 @@ "@microsoft/applicationinsights-shims" "2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-dependencies-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.8.4.tgz#b9afbb81fb44aeb3033ecff1d2e4d33d71c1d41c" - integrity sha512-dr11EBFBR+vmtTipubZv9KSWRXLk6XdutkEgilgzXdSFun0dqR+ZSHEmMWyqE8ZsJtW+1HzdKuGtODSQY6uHyw== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-properties-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.8.4.tgz#c52a6ce03b8f99b2110a097b4ef30686dfb433f8" - integrity sha512-UI0afK5e8yUJ1qIdy+7FA/G9TB+st0++trx4bUMa+Hb6gJggdQPq94lBFJL0yzo4QsgQwozVwkInXy4534tTYQ== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== -"@microsoft/applicationinsights-web@^2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.8.4.tgz#6b385d385790b9574dad0754b860e656cde470b7" - integrity sha512-3CtZiM6e5Q0AA+1NE4k8A0+Y0FE1jsK4u0sb4AkvV7b4cwb86I9l7F7fQPU+V/ltkni0g2WtDrMNU93RuxSmNw== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.8.4" - "@microsoft/applicationinsights-channel-js" "2.8.4" - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-dependencies-js" "2.8.4" - "@microsoft/applicationinsights-properties-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/dynamicproto-js@^1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" @@ -131,22 +68,22 @@ vscode-textmate@7.0.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== -xterm-addon-search@0.9.0-beta.39: - version "0.9.0-beta.39" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.39.tgz#e8376e1485ee7d763c07d1a8f1354114f65b3e3e" - integrity sha512-h45wkecgfqXXoAUqgNytAfSd6g0xNT6rZy/enVaEU0aes7QoL9pxHUKkCry8PP6hs03Slk0VxQ4AGsbSZGvK/w== +xterm-addon-search@0.10.0-beta.1: + version "0.10.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.1.tgz#ee15b954b6f78585cd3a212ec662018263470266" + integrity sha512-rp68SwoYHIQ1SY4MoILNK+0HcN8OR4hzczHOYCFdeKYZFvH/16vgqg0OJT6t6WlL1cq971rLsEDXT1SKcpoJqA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.41: - version "0.12.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.41.tgz#17dbca975b6e9b34526ebc57f4de59ee295da1b6" - integrity sha512-wvQxC5diMYEJEMaILfz+4CWB2GgtzjzNQRNDnK7R7Y9wDI+P4idDlQKgyH0nA93sl9R4zgqlBVha//wuq4vfZg== +xterm-addon-webgl@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" + integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== -xterm@4.19.0-beta.60: - version "4.19.0-beta.60" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.60.tgz#de89d93153ae3ec17b53f3e5d03b8ea859126081" - integrity sha512-YQjvp4xtSAyNm9+gF4OA5+QzkSWdKMCy4WABETpe7CsrEGx+mJVoXCZ9wgopmvwRXz7DeJvKM5dWz1HPjVLDtA== +xterm@4.20.0-beta.5: + version "4.20.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" + integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== diff --git a/remote/yarn.lock b/remote/yarn.lock index 6ee84100308..bc084bb74dc 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -20,35 +20,6 @@ "@microsoft/applicationinsights-shims" "^2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-analytics-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.8.4.tgz#3b32d8a2122be5d5993c74ef3217ebbf4876ea69" - integrity sha512-n/FPs8SS6rB8h+u157fiRh0TwUWKctxGNvr4M+LKeSdgDvf9c759gUeMR7r8xF6kBBfgkbmyaVORjsA1WJsU4g== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-channel-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.4.tgz#6de7210d87e61c72d3a9a06cbaeae14e1b543484" - integrity sha512-aml49Jya8LxX4tvyBbIvcxSo7UGI0k3HeiJQRFLeO+QlA+Ocsl10PqphU/OYJ4hh/P5/2QhEAq5bBM/b9/PNrg== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-common@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.4.tgz#45b422cf1804df06d5abb2ceda5ed65268a92135" - integrity sha512-uDvd4zxNGNYFE0TF4h7tAg+eMIPatyd1QdkP8fA4UYwshF4/+UwS1wegjXLEWQRRH87+UAyvx4IKQjobzzEX0A== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-core-js@2.8.4": version "2.8.4" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" @@ -57,45 +28,11 @@ "@microsoft/applicationinsights-shims" "2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-dependencies-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.8.4.tgz#b9afbb81fb44aeb3033ecff1d2e4d33d71c1d41c" - integrity sha512-dr11EBFBR+vmtTipubZv9KSWRXLk6XdutkEgilgzXdSFun0dqR+ZSHEmMWyqE8ZsJtW+1HzdKuGtODSQY6uHyw== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-properties-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.8.4.tgz#c52a6ce03b8f99b2110a097b4ef30686dfb433f8" - integrity sha512-UI0afK5e8yUJ1qIdy+7FA/G9TB+st0++trx4bUMa+Hb6gJggdQPq94lBFJL0yzo4QsgQwozVwkInXy4534tTYQ== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== -"@microsoft/applicationinsights-web@^2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.8.4.tgz#6b385d385790b9574dad0754b860e656cde470b7" - integrity sha512-3CtZiM6e5Q0AA+1NE4k8A0+Y0FE1jsK4u0sb4AkvV7b4cwb86I9l7F7fQPU+V/ltkni0g2WtDrMNU93RuxSmNw== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.8.4" - "@microsoft/applicationinsights-channel-js" "2.8.4" - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-dependencies-js" "2.8.4" - "@microsoft/applicationinsights-properties-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/dynamicproto-js@^1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" @@ -168,16 +105,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -applicationinsights@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" - integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -191,21 +118,6 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" - -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -245,15 +157,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== - dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -264,14 +167,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - cookie@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" @@ -325,25 +220,6 @@ detect-libc@^2.0.0: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -diagnostic-channel-publishers@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" - integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - integrity sha512-awkcaaNNi0RfUGJf7r2+K4oJs1OyiIG2m/Jwvyi0OeQxdw+UU/iwbiejTPa3tUeyXtBcp2fef0JOJNdD62r/zg== - dependencies: - semver "^5.3.0" - -emitter-listener@^1.0.1, emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -706,16 +582,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -semver@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@^5.4.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -728,11 +594,6 @@ set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - signal-exit@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" @@ -783,11 +644,6 @@ spdlog@^0.13.0: mkdirp "^0.5.5" nan "^2.14.0" -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -932,35 +788,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.9.0-beta.39: - version "0.9.0-beta.39" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.39.tgz#e8376e1485ee7d763c07d1a8f1354114f65b3e3e" - integrity sha512-h45wkecgfqXXoAUqgNytAfSd6g0xNT6rZy/enVaEU0aes7QoL9pxHUKkCry8PP6hs03Slk0VxQ4AGsbSZGvK/w== +xterm-addon-search@0.10.0-beta.1: + version "0.10.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.1.tgz#ee15b954b6f78585cd3a212ec662018263470266" + integrity sha512-rp68SwoYHIQ1SY4MoILNK+0HcN8OR4hzczHOYCFdeKYZFvH/16vgqg0OJT6t6WlL1cq971rLsEDXT1SKcpoJqA== -xterm-addon-serialize@0.7.0-beta.13: - version "0.7.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.13.tgz#5c859c8657cab7f28405aab1a0715daf54bc7714" - integrity sha512-TYFlm/gds0pOmpzXw7ZWx8Cy48lMaOZZqZgfm5pWU37HPvzfKxXSVdYL1biWpRCH2zCH+3cWmOma8W1pBRr+Eg== +xterm-addon-serialize@0.8.0-beta.1: + version "0.8.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.1.tgz#d1496da20006afa81874a717e3a0f75fc71dc87a" + integrity sha512-CfS0do/GM8e3k0+3O6GNDi4Gbhhkx1ne1nnnkILWQaAmlArLySEL8f0uPR0W72AtlLEFwVF8kABbVTjKc5XUcA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.41: - version "0.12.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.41.tgz#17dbca975b6e9b34526ebc57f4de59ee295da1b6" - integrity sha512-wvQxC5diMYEJEMaILfz+4CWB2GgtzjzNQRNDnK7R7Y9wDI+P4idDlQKgyH0nA93sl9R4zgqlBVha//wuq4vfZg== +xterm-addon-webgl@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" + integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== -xterm-headless@4.19.0-beta.60: - version "4.19.0-beta.60" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.60.tgz#ccd09a7c69ba487d3fd87d6a8d159f1a6a10e9b2" - integrity sha512-mJ24BRnEDCASJDTGFZysZxiXkCmed3eMFiWCPvIF48LVrqxt1cImoVKBlxqkdgUkHKjfF2c92S1TTBL5aT1I0w== +xterm-headless@4.20.0-beta.5: + version "4.20.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641" + integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw== -xterm@4.19.0-beta.60: - version "4.19.0-beta.60" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.60.tgz#de89d93153ae3ec17b53f3e5d03b8ea859126081" - integrity sha512-YQjvp4xtSAyNm9+gF4OA5+QzkSWdKMCy4WABETpe7CsrEGx+mJVoXCZ9wgopmvwRXz7DeJvKM5dWz1HPjVLDtA== +xterm@4.20.0-beta.5: + version "4.20.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" + integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== yallist@^4.0.0: version "4.0.0" diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index 7eea9ff555d..00ddb6fdf08 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -21,6 +21,7 @@ Visual Studio Code is a new choice of tool that combines the simplicity of a cod %define _build_id_links none %install +mkdir -p %{buildroot}/usr/bin mkdir -p %{buildroot}/usr/share/@@NAME@@ mkdir -p %{buildroot}/usr/share/applications mkdir -p %{buildroot}/usr/share/pixmaps @@ -34,6 +35,7 @@ cp -r usr/share/mime/packages/@@NAME@@-workspace.xml %{buildroot}/usr/share/mime cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ +ln -s ../share/@@NAME@@/bin/@@NAME@@ %{buildroot}/usr/bin/@@NAME@@ %post # Remove the legacy bin command if this is the stable build @@ -41,9 +43,6 @@ if [ "@@NAME@@" = "code" ]; then rm -f /usr/local/bin/code fi -# Symlink bin command to /usr/bin -ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@ - # Register yum repository # TODO: #229: Enable once the yum repository is signed #if [ "@@NAME@@" != "code-oss" ]; then @@ -58,10 +57,6 @@ ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@ update-mime-database /usr/share/mime &> /dev/null || : %postun -if [ $1 = 0 ]; then - rm -f /usr/bin/@@NAME@@ -fi - # Update mimetype database for removed workspace mimetype update-mime-database /usr/share/mime &> /dev/null || : @@ -69,6 +64,7 @@ update-mime-database /usr/share/mime &> /dev/null || : %defattr(-,root,root) %attr(4755, root, root) /usr/share/@@NAME@@/chrome-sandbox +/usr/bin/@@NAME@@ /usr/share/@@NAME@@/ /usr/share/applications/@@NAME@@.desktop /usr/share/applications/@@NAME@@-url-handler.desktop diff --git a/src/bootstrap.js b/src/bootstrap.js index 319ddba9b02..a8970d05c4a 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -205,7 +205,7 @@ } /** - * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process} + * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process | undefined} */ function safeProcess() { const sandboxGlobals = safeSandboxGlobals(); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index edb106eb05d..4d08566a4f6 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1734,6 +1734,19 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly return { top, right, bottom, left }; } +interface DomNodeAttributes { + role?: string; + ariaHidden?: boolean; + style?: StyleAttributes; +} + +interface StyleAttributes { + height?: number | string; + width?: number | string; +} + +// + /** * A helper function to create nested dom nodes. * @@ -1749,22 +1762,22 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly * private readonly editor = createEditor(this.htmlElements.editor); * ``` */ -export function h(tag: TTag): never; export function h( tag: TTag, - attributes: { $: TId } + attributes: { $: TId } & DomNodeAttributes ): Record>; +export function h(tag: TTag, attributes: DomNodeAttributes): Record<'root', TagToElement>; export function h)[]>( tag: TTag, children: T ): (ArrayToObj & Record<'root', TagToElement>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; export function h)[]>( tag: TTag, - attributes: { $: TId }, + attributes: { $: TId } & DomNodeAttributes, children: T ): (ArrayToObj & Record>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h(tag: string, ...args: [] | [attributes: { $: string } | Record, children?: any[]] | [children: any[]]): Record { - let attributes: Record; +export function h(tag: string, ...args: [] | [attributes: { $: string } & DomNodeAttributes | Record, children?: any[]] | [children: any[]]): Record { + let attributes: { $?: string } & DomNodeAttributes; let children: (Record | HTMLElement)[] | undefined; if (Array.isArray(args[0])) { @@ -1801,7 +1814,16 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } | Record result[value] = el; continue; } - el.setAttribute(key, value); + if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + el.style.setProperty( + camelCaseToHyphenCase(cssKey), + typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue + ); + } + continue; + } + el.setAttribute(camelCaseToHyphenCase(key), value.toString()); } result['root'] = el; @@ -1809,6 +1831,10 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } | Record return result; } +function camelCaseToHyphenCase(str: string) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} + type RemoveHTMLElement = T extends HTMLElement ? never : T; type ArrayToObj = UnionToIntersection>; diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index ed4131eefd3..5327fb0e1e3 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -38,6 +38,10 @@ cursor: pointer; } +.monaco-button-dropdown > .monaco-button:focus { + outline-offset: -1px !important; +} + .monaco-button-dropdown > .monaco-dropdown-button { margin-left: 1px; } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index a2349f9dc96..3187254843b 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -15,6 +15,7 @@ import { Emitter, Event as BaseEvent } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; +import { localize } from 'vs/nls'; import 'vs/css!./button'; export interface IButtonOptions extends IButtonStyles { @@ -263,6 +264,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined))); this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); + this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); this.dropdownButton.icon = Codicon.dropDownButton; this._register(this.dropdownButton.onDidClick(e => { diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 20ffef74fa8..13999090718 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 20df7f37ba8..d97fe00bbf2 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -258,6 +258,22 @@ export function isMonacoEditor(e: HTMLElement): boolean { return isMonacoEditor(e.parentElement); } +export function isButton(e: HTMLElement): boolean { + if (e.tagName === 'A' && e.classList.contains('monaco-button')) { + return true; + } + + if (e.classList.contains('monaco-list')) { + return false; + } + + if (!e.parentElement) { + return false; + } + + return isButton(e.parentElement); +} + class KeyboardController implements IDisposable { private readonly disposables = new DisposableStore(); diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css index 80ef6a39a57..64e309bf5dc 100644 --- a/src/vs/base/browser/ui/menu/menubar.css +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -27,7 +27,6 @@ zoom: 1; white-space: nowrap; outline: 0 !important; - align-self: center; } .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 82e5c1b811a..10dd697e7a4 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -979,7 +979,7 @@ export class MenuBar extends Disposable { const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex; const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menus[actualMenuIndex]; - if (!customMenu.actions || !customMenu.buttonElement) { + if (!customMenu.actions || !customMenu.buttonElement || !customMenu.titleElement) { return; } @@ -987,19 +987,19 @@ export class MenuBar extends Disposable { customMenu.buttonElement.classList.add('open'); - const buttonBoundingRect = customMenu.buttonElement.getBoundingClientRect(); - const buttonBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.buttonElement); + const titleBoundingRect = customMenu.titleElement.getBoundingClientRect(); + const titleBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.titleElement); if (this.options.compactMode === Direction.Right) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; - menuHolder.style.left = `${buttonBoundingRect.left + this.container.clientWidth}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; + menuHolder.style.left = `${titleBoundingRect.left + this.container.clientWidth}px`; } else if (this.options.compactMode === Direction.Left) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; menuHolder.style.right = `${this.container.clientWidth}px`; menuHolder.style.left = 'auto'; } else { - menuHolder.style.top = `${buttonBoundingRect.bottom * buttonBoundingRectZoom}px`; - menuHolder.style.left = `${buttonBoundingRect.left * buttonBoundingRectZoom}px`; + menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`; + menuHolder.style.left = `${titleBoundingRect.left * titleBoundingRectZoom}px`; } customMenu.buttonElement.appendChild(menuHolder); diff --git a/src/vs/editor/contrib/suggest/browser/resizable.ts b/src/vs/base/browser/ui/resizable/resizable.ts similarity index 100% rename from src/vs/editor/contrib/suggest/browser/resizable.ts rename to src/vs/base/browser/ui/resizable/resizable.ts diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c03f5c5e1af..124d926541f 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -9,7 +9,7 @@ import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget'; +import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays'; @@ -1153,7 +1153,9 @@ class TreeNodeListMouseController extends MouseController< } protected override onViewPointer(e: IListMouseEvent>): void { - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isButton(e.browserEvent.target as HTMLElement) || + isInputElement(e.browserEvent.target as HTMLElement) || + isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index b452a203600..9e696833141 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -558,6 +558,7 @@ export class Codicon implements CSSIcon { public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' }); public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' }); public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' }); + public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\f101' }); // derived icons, that could become separate icons diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 1f16cd438ea..d8ee92f757e 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -15,20 +15,6 @@ export type IStringDictionary = Record; */ export type INumberDictionary = Record; -/** - * Iterates over each entry in the provided dictionary. The iterator will stop when the callback returns `false`. - * - * @deprecated Use `Object.entries(x)` with a `for...of` loop. - */ -export function forEach(from: IStringDictionary | INumberDictionary, callback: (entry: { key: any; value: T }) => any): void { - for (const [key, value] of Object.entries(from)) { - const result = callback({ key, value }); - if (result === false) { - return; - } - } -} - /** * Groups the collection into a dictionary based on the provided * group function. diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 647b5f1beed..d395e4a9f32 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IObservable, IObserver } from 'vs/base/common/observableImpl/base'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -423,6 +424,55 @@ export namespace Event { store?.dispose(); }); } + + class EmitterObserver implements IObserver { + + readonly emitter: Emitter; + + private _counter = 0; + private _hasChanged = false; + + constructor(readonly obs: IObservable, store: DisposableStore | undefined) { + const options = { + onFirstListenerAdd: () => { + obs.addObserver(this); + }, + onLastListenerRemove: () => { + obs.removeObserver(this); + } + }; + if (!store) { + _addLeakageTraceLogic(options); + } + this.emitter = new Emitter(options); + if (store) { + store.add(this.emitter); + } + } + + beginUpdate(_observable: IObservable): void { + // console.assert(_observable === this.obs); + this._counter++; + } + + handleChange(_observable: IObservable, _change: TChange): void { + this._hasChanged = true; + } + + endUpdate(_observable: IObservable): void { + if (--this._counter === 0) { + if (this._hasChanged) { + this._hasChanged = false; + this.emitter.fire(this.obs.get()); + } + } + } + } + + export function fromObservable(obs: IObservable, store?: DisposableStore): Event { + const observer = new EmitterObserver(obs, store); + return observer.emitter.event; + } } export interface EmitterOptions { diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts new file mode 100644 index 00000000000..c588d68b9f6 --- /dev/null +++ b/src/vs/base/common/observable.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { + IObservable, + IObserver, + IReader, + ISettable, + ISettableObservable, + ITransaction, + observableValue, + transaction, +} from 'vs/base/common/observableImpl/base'; +export { derived } from 'vs/base/common/observableImpl/derived'; +export { + autorun, + autorunDelta, + autorunHandleChanges, + autorunWithStore, +} from 'vs/base/common/observableImpl/autorun'; +export * from 'vs/base/common/observableImpl/utils'; + +import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableImpl/logging'; + +const enableLogging = false; +if (enableLogging) { + setLogger(new ConsoleObservableLogger()); +} diff --git a/src/vs/base/common/observableImpl/autorun.ts b/src/vs/base/common/observableImpl/autorun.ts new file mode 100644 index 00000000000..6efe4736783 --- /dev/null +++ b/src/vs/base/common/observableImpl/autorun.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver(debugName, fn, undefined); +} + +interface IChangeContext { + readonly changedObservable: IObservable; + readonly change: unknown; + + didChange(observable: IObservable): this is { change: TChange }; +} + +export function autorunHandleChanges( + debugName: string, + options: { + /** + * Returns if this change should cause a re-run of the autorun. + */ + handleChange: (context: IChangeContext) => boolean; + }, + fn: (reader: IReader) => void +): IDisposable { + return new AutorunObserver(debugName, fn, options.handleChange); +} + +export function autorunWithStore( + fn: (reader: IReader, store: DisposableStore) => void, + debugName: string +): IDisposable { + const store = new DisposableStore(); + const disposable = autorun( + debugName, + reader => { + store.clear(); + fn(reader, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +export class AutorunObserver implements IObserver, IReader, IDisposable { + public needsToRun = true; + private updateCount = 0; + private disposed = false; + + /** + * The actual dependencies. + */ + private _dependencies = new Set>(); + public get dependencies() { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + constructor( + public readonly debugName: string, + private readonly runFn: (reader: IReader) => void, + private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined + ) { + getLogger()?.handleAutorunCreated(this); + this.runIfNeeded(); + } + + public subscribeTo(observable: IObservable) { + // In case the run action disposes the autorun + if (this.disposed) { + return; + } + this._dependencies.add(observable); + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + public handleChange(observable: IObservable, change: TChange): void { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: o => o === observable as any, + }) : true; + this.needsToRun = this.needsToRun || shouldReact; + + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + public beginUpdate(): void { + this.updateCount++; + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + private runIfNeeded(): void { + if (!this.needsToRun) { + return; + } + // Assert: this.staleDependencies is an empty set. + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + this.needsToRun = false; + + getLogger()?.handleAutorunTriggered(this); + + try { + this.runFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + } + + public dispose(): void { + this.disposed = true; + for (const o of this._dependencies) { + o.removeObserver(this); + } + this._dependencies.clear(); + } + + public toString(): string { + return `Autorun<${this.debugName}>`; + } +} + +export namespace autorun { + export const Observer = AutorunObserver; +} +export function autorunDelta( + name: string, + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorun(name, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts new file mode 100644 index 00000000000..9d83083e49b --- /dev/null +++ b/src/vs/base/common/observableImpl/base.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { derived } from 'vs/base/common/observableImpl/derived'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export interface IObservable { + readonly TChange: TChange; + + /** + * Reads the current value. + * + * Must not be called from {@link IObserver.handleChange}. + */ + get(): T; + + /** + * Adds an observer. + */ + addObserver(observer: IObserver): void; + removeObserver(observer: IObserver): void; + + /** + * Subscribes the reader to this observable and returns the current value of this observable. + */ + read(reader: IReader): T; + + map(fn: (value: T) => TNew): IObservable; + + readonly debugName: string; +} + +export interface IReader { + /** + * Reports an observable that was read. + * + * Is called by {@link IObservable.read}. + */ + subscribeTo(observable: IObservable): void; +} + +export interface IObserver { + /** + * Indicates that an update operation is about to begin. + * + * During an update, invariants might not hold for subscribed observables and + * change events might be delayed. + * However, all changes must be reported before all update operations are over. + */ + beginUpdate(observable: IObservable): void; + + /** + * Is called by a subscribed observable immediately after it notices a change. + * + * When {@link IObservable.get} returns and no change has been reported, + * there has been no change for that observable. + * + * Implementations must not call into other observables! + * The change should be processed when {@link IObserver.endUpdate} is called. + */ + handleChange(observable: IObservable, change: TChange): void; + + /** + * Indicates that an update operation has completed. + */ + endUpdate(observable: IObservable): void; +} + +export interface ISettable { + set(value: T, transaction: ITransaction | undefined, change: TChange): void; +} + +export interface ITransaction { + /** + * Calls `Observer.beginUpdate` immediately + * and `Observer.endUpdate` when the transaction is complete. + */ + updateObserver( + observer: IObserver, + observable: IObservable + ): void; +} + +let _derived: typeof derived; +/** + * @internal + * This is to allow splitting files. +*/ +export function _setDerived(derived: typeof _derived) { + _derived = derived; +} + +export abstract class ConvenientObservable implements IObservable { + get TChange(): TChange { return null!; } + + public abstract get(): T; + public abstract addObserver(observer: IObserver): void; + public abstract removeObserver(observer: IObserver): void; + + /** @sealed */ + public read(reader: IReader): T { + reader.subscribeTo(this); + return this.get(); + } + + /** @sealed */ + public map(fn: (value: T) => TNew): IObservable { + return _derived( + () => { + const name = getFunctionName(fn); + return name !== undefined ? name : `${this.debugName} (mapped)`; + }, + (reader) => fn(this.read(reader)) + ); + } + + public abstract get debugName(): string; +} + +export abstract class BaseObservable extends ConvenientObservable { + protected readonly observers = new Set(); + + /** @sealed */ + public addObserver(observer: IObserver): void { + const len = this.observers.size; + this.observers.add(observer); + if (len === 0) { + this.onFirstObserverAdded(); + } + } + + /** @sealed */ + public removeObserver(observer: IObserver): void { + const deleted = this.observers.delete(observer); + if (deleted && this.observers.size === 0) { + this.onLastObserverRemoved(); + } + } + + protected onFirstObserverAdded(): void { } + protected onLastObserverRemoved(): void { } +} + +export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + const tx = new TransactionImpl(fn, getDebugName); + try { + getLogger()?.handleBeginTransaction(tx); + fn(tx); + } finally { + tx.finish(); + getLogger()?.handleEndTransaction(); + } +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} + +export class TransactionImpl implements ITransaction { + private updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; + + constructor(private readonly fn: Function, private readonly _getDebugName?: () => string) { } + + public getDebugName(): string | undefined { + if (this._getDebugName) { + return this._getDebugName(); + } + return getFunctionName(this.fn); + } + + public updateObserver( + observer: IObserver, + observable: IObservable + ): void { + this.updatingObservers!.push({ observer, observable }); + observer.beginUpdate(observable); + } + + public finish(): void { + const updatingObservers = this.updatingObservers!; + // Prevent anyone from updating observers from now on. + this.updatingObservers = null; + for (const { observer, observable } of updatingObservers) { + observer.endUpdate(observable); + } + } +} + +export interface ISettableObservable extends IObservable, ISettable { +} + +export function observableValue(name: string, initialValue: T): ISettableObservable { + return new ObservableValue(name, initialValue); +} + +export class ObservableValue + extends BaseObservable + implements ISettableObservable +{ + private value: T; + + constructor(public readonly debugName: string, initialValue: T) { + super(); + this.value = initialValue; + } + + public get(): T { + return this.value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (this.value === value) { + return; + } + + if (!tx) { + transaction((tx) => { + this.set(value, tx, change); + }, () => `Setting ${this.debugName}`); + return; + } + + const oldValue = this.value; + this.value = value; + getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true }); + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } + + override toString(): string { + return `${this.debugName}: ${this.value}`; + } +} + diff --git a/src/vs/base/common/observableImpl/derived.ts b/src/vs/base/common/observableImpl/derived.ts new file mode 100644 index 00000000000..84a93132f15 --- /dev/null +++ b/src/vs/base/common/observableImpl/derived.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function derived(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable { + return new Derived(debugName, computeFn); +} + +_setDerived(derived); + +export class Derived extends BaseObservable implements IReader, IObserver { + private hadValue = false; + private hasValue = false; + private value: T | undefined = undefined; + private updateCount = 0; + + private _dependencies = new Set>(); + public get dependencies(): ReadonlySet> { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + public override get debugName(): string { + return typeof this._debugName === 'function' ? this._debugName() : this._debugName; + } + + constructor( + private readonly _debugName: string | (() => string), + private readonly computeFn: (reader: IReader) => T + ) { + super(); + + getLogger()?.handleDerivedCreated(this); + } + + protected override onLastObserverRemoved(): void { + /** + * We are not tracking changes anymore, thus we have to assume + * that our cache is invalid. + */ + this.hasValue = false; + this.hadValue = false; + this.value = undefined; + for (const d of this._dependencies) { + d.removeObserver(this); + } + this._dependencies.clear(); + } + + public get(): T { + if (this.observers.size === 0) { + // Cache is not valid and don't refresh the cache. + // Observables should not be read in non-reactive contexts. + const result = this.computeFn(this); + // Clear new dependencies + this.onLastObserverRemoved(); + return result; + } + + if (this.updateCount > 0 && this.hasValue) { + // Refresh dependencies + for (const d of this._dependencies) { + // Maybe `.get()` triggers `handleChange`? + d.get(); + if (!this.hasValue) { + // The other dependencies will refresh on demand + break; + } + } + } + + if (!this.hasValue) { + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + const oldValue = this.value; + try { + this.value = this.computeFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + + this.hasValue = true; + const didChange = this.hadValue && oldValue !== this.value; + getLogger()?.handleDerivedRecomputed(this, { + oldValue, + newValue: this.value, + change: undefined, + didChange + }); + if (didChange) { + for (const r of this.observers) { + r.handleChange(this, undefined); + } + } + } + return this.value!; + } + + // IObserver Implementation + public beginUpdate(): void { + if (this.updateCount === 0) { + for (const r of this.observers) { + r.beginUpdate(this); + } + } + this.updateCount++; + } + + public handleChange( + _observable: IObservable, + _change: TChange + ): void { + if (this.hasValue) { + this.hadValue = true; + this.hasValue = false; + } + + // Not in transaction: Recompute & inform observers immediately + if (this.updateCount === 0 && this.observers.size > 0) { + this.get(); + } + + // Otherwise, recompute in `endUpdate` or on demand. + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + if (this.observers.size > 0) { + // Propagate invalidation + this.get(); + } + + for (const r of this.observers) { + r.endUpdate(this); + } + } + } + + // IReader Implementation + public subscribeTo(observable: IObservable) { + this._dependencies.add(observable); + // We are already added as observer for stale dependencies. + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + override toString(): string { + return `LazyDerived<${this.debugName}>`; + } +} diff --git a/src/vs/base/common/observableImpl/logging.ts b/src/vs/base/common/observableImpl/logging.ts new file mode 100644 index 00000000000..0c221d0c700 --- /dev/null +++ b/src/vs/base/common/observableImpl/logging.ts @@ -0,0 +1,312 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AutorunObserver } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, ObservableValue, TransactionImpl } from 'vs/base/common/observableImpl/base'; +import { Derived } from 'vs/base/common/observableImpl/derived'; +import { FromEventObservable } from 'vs/base/common/observableImpl/utils'; + +let globalObservableLogger: IObservableLogger | undefined; + +export function setLogger(logger: IObservableLogger): void { + globalObservableLogger = logger; +} + +export function getLogger(): IObservableLogger | undefined { + return globalObservableLogger; +} + +interface IChangeInformation { + oldValue: unknown; + newValue: unknown; + change: unknown; + didChange: boolean; +} + +export interface IObservableLogger { + handleObservableChanged(observable: ObservableValue, info: IChangeInformation): void; + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; + + handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunTriggered(autorun: AutorunObserver): void; + + handleDerivedCreated(observable: Derived): void; + handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; + + handleBeginTransaction(transaction: TransactionImpl): void; + handleEndTransaction(): void; +} + +export class ConsoleObservableLogger implements IObservableLogger { + private indentation = 0; + + private textToConsoleArgs(text: ConsoleText): unknown[] { + return consoleTextToArgs([ + normalText(repeat('| ', this.indentation)), + text, + ]); + } + + private formatInfo(info: IChangeInformation): ConsoleText[] { + return info.didChange + ? [ + normalText(` `), + styled(formatValue(info.oldValue, 70), { + color: 'red', + strikeThrough: true, + }), + normalText(` `), + styled(formatValue(info.newValue, 60), { + color: 'green', + }), + ] + : [normalText(` (unchanged)`)]; + } + + handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable value changed'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + private readonly changedObservablesSets = new WeakMap>>(); + + formatChanges(changes: Set>): ConsoleText | undefined { + if (changes.size === 0) { + return undefined; + } + return styled( + ' (changed deps: ' + + [...changes].map((o) => o.debugName).join(', ') + + ')', + { color: 'gray' } + ); + } + + handleDerivedCreated(derived: Derived): void { + const existingHandleChange = derived.handleChange; + this.changedObservablesSets.set(derived, new Set()); + derived.handleChange = (observable, change) => { + this.changedObservablesSets.get(derived)!.add(observable); + return existingHandleChange.apply(derived, [observable, change]); + }; + } + + handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { + const changedObservables = this.changedObservablesSets.get(derived)!; + console.log(...this.textToConsoleArgs([ + formatKind('derived recomputed'), + styled(derived.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable from event triggered'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + handleAutorunCreated(autorun: AutorunObserver): void { + const existingHandleChange = autorun.handleChange; + this.changedObservablesSets.set(autorun, new Set()); + autorun.handleChange = (observable, change) => { + this.changedObservablesSets.get(autorun)!.add(observable); + return existingHandleChange.apply(autorun, [observable, change]); + }; + } + + handleAutorunTriggered(autorun: AutorunObserver): void { + const changedObservables = this.changedObservablesSets.get(autorun)!; + console.log(...this.textToConsoleArgs([ + formatKind('autorun'), + styled(autorun.debugName, { color: 'BlueViolet' }), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleBeginTransaction(transaction: TransactionImpl): void { + let transactionName = transaction.getDebugName(); + if (transactionName === undefined) { + transactionName = ''; + } + console.log(...this.textToConsoleArgs([ + formatKind('transaction'), + styled(transactionName, { color: 'BlueViolet' }), + ])); + this.indentation++; + } + + handleEndTransaction(): void { + this.indentation--; + } +} + +type ConsoleText = + | (ConsoleText | undefined)[] + | { text: string; style: string; data?: Record } + | { data: Record }; + +function consoleTextToArgs(text: ConsoleText): unknown[] { + const styles = new Array(); + const initial = {}; + const data = initial; + let firstArg = ''; + + function process(t: ConsoleText): void { + if ('length' in t) { + for (const item of t) { + if (item) { + process(item); + } + } + } else if ('text' in t) { + firstArg += `%c${t.text}`; + styles.push(t.style); + if (t.data) { + Object.assign(data, t.data); + } + } else if ('data' in t) { + Object.assign(data, t.data); + } + } + + process(text); + + const result = [firstArg, ...styles]; + if (Object.keys(data).length > 0) { + result.push(data); + } + + return result; +} + +function normalText(text: string): ConsoleText { + return styled(text, { color: 'black' }); +} + +function formatKind(kind: string): ConsoleText { + return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true }); +} + +function styled( + text: string, + options: { color: string; strikeThrough?: boolean; bold?: boolean } = { + color: 'black', + } +): ConsoleText { + function objToCss(styleObj: Record): string { + return Object.entries(styleObj).reduce( + (styleString, [propName, propValue]) => { + return `${styleString}${propName}:${propValue};`; + }, + '' + ); + } + + const style: Record = { + color: options.color, + }; + if (options.strikeThrough) { + style['text-decoration'] = 'line-through'; + } + if (options.bold) { + style['font-weight'] = 'bold'; + } + + return { + text, + style: objToCss(style), + }; +} + +function formatValue(value: unknown, availableLen: number): string { + switch (typeof value) { + case 'number': + return '' + value; + case 'string': + if (value.length + 2 <= availableLen) { + return `"${value}"`; + } + return `"${value.substr(0, availableLen - 7)}"+...`; + + case 'boolean': + return value ? 'true' : 'false'; + case 'undefined': + return 'undefined'; + case 'object': + if (value === null) { + return 'null'; + } + if (Array.isArray(value)) { + return formatArray(value, availableLen); + } + return formatObject(value, availableLen); + case 'symbol': + return value.toString(); + case 'function': + return `[[Function${value.name ? ' ' + value.name : ''}]]`; + default: + return '' + value; + } +} + +function formatArray(value: unknown[], availableLen: number): string { + let result = '[ '; + let first = true; + for (const val of value) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${formatValue(val, availableLen - result.length)}`; + } + result += ' ]'; + return result; +} + +function formatObject(value: object, availableLen: number): string { + let result = '{ '; + let first = true; + for (const [key, val] of Object.entries(value)) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${key}: ${formatValue(val, availableLen - result.length)}`; + } + result += ' }'; + return result; +} + +function repeat(str: string, count: number): string { + let result = ''; + for (let i = 1; i <= count; i++) { + result += str; + } + return result; +} + +function padStr(str: string, length: number): string { + while (str.length < length) { + str += ' '; + } + return str; +} diff --git a/src/vs/base/common/observableImpl/utils.ts b/src/vs/base/common/observableImpl/utils.ts new file mode 100644 index 00000000000..0b07d089b83 --- /dev/null +++ b/src/vs/base/common/observableImpl/utils.ts @@ -0,0 +1,281 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorun } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, BaseObservable, transaction, IReader, ITransaction, ConvenientObservable, IObserver, observableValue, getFunctionName } from 'vs/base/common/observableImpl/base'; +import { derived } from 'vs/base/common/observableImpl/derived'; +import { Event } from 'vs/base/common/event'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function constObservable(value: T): IObservable { + return new ConstObservable(value); +} + +class ConstObservable extends ConvenientObservable { + constructor(private readonly value: T) { + super(); + } + + public override get debugName(): string { + return this.toString(); + } + + public get(): T { + return this.value; + } + public addObserver(observer: IObserver): void { + // NO OP + } + public removeObserver(observer: IObserver): void { + // NO OP + } + + override toString(): string { + return `Const: ${this.value}`; + } +} + + +export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { + const observable = observableValue<{ value?: T }>('promiseValue', {}); + promise.then((value) => { + observable.set({ value }, undefined); + }); + return observable; +} + +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { + return new Promise(resolve => { + const d = autorun('waitForState', reader => { + const currentState = observable.read(reader); + if (predicate(currentState)) { + d.dispose(); + resolve(currentState); + } + }); + }); +} + +export function observableFromEvent( + event: Event, + getValue: (args: TArgs | undefined) => T +): IObservable { + return new FromEventObservable(event, getValue); +} + +export class FromEventObservable extends BaseObservable { + private value: T | undefined; + private hasValue = false; + private subscription: IDisposable | undefined; + + constructor( + private readonly event: Event, + private readonly getValue: (args: TArgs | undefined) => T + ) { + super(); + } + + private getDebugName(): string | undefined { + return getFunctionName(this.getValue); + } + + public get debugName(): string { + const name = this.getDebugName(); + return 'From Event' + (name ? `: ${name}` : ''); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = (args: TArgs | undefined) => { + const newValue = this.getValue(args); + + const didChange = this.value !== newValue; + + getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange }); + + if (didChange) { + this.value = newValue; + + if (this.hasValue) { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => { + const name = this.getDebugName(); + return 'Event fired' + (name ? `: ${name}` : ''); + } + ); + } + this.hasValue = true; + } + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + this.hasValue = false; + this.value = undefined; + } + + public get(): T { + if (this.subscription) { + if (!this.hasValue) { + this.handleEvent(undefined); + } + return this.value!; + } else { + // no cache, as there are no subscribers to keep it updated + return this.getValue(undefined); + } + } +} + +export namespace observableFromEvent { + export const Observer = FromEventObservable; +} + +export function observableSignalFromEvent( + debugName: string, + event: Event +): IObservable { + return new FromEventObservableSignal(debugName, event); +} + +class FromEventObservableSignal extends BaseObservable { + private subscription: IDisposable | undefined; + + constructor( + public readonly debugName: string, + private readonly event: Event, + ) { + super(); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = () => { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => this.debugName + ); + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + } + + public override get(): void { + // NO OP + } +} + +export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { + const debouncedObservable = observableValue('debounced', undefined); + + let timeout: any = undefined; + + disposableStore.add(autorun('debounce', reader => { + const value = observable.read(reader); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + transaction(tx => { + debouncedObservable.set(value, tx); + }); + }, debounceMs); + + })); + + return debouncedObservable; +} + +export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { + const observable = observableValue('triggeredRecently', false); + + let timeout: any = undefined; + + disposableStore.add(event(() => { + observable.set(true, undefined); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + observable.set(false, undefined); + }, timeoutMs); + })); + + return observable; +} + +/** + * This ensures the observable is kept up-to-date. + * This is useful when the observables `get` method is used. +*/ +export function keepAlive(observable: IObservable): IDisposable { + const o = new KeepAliveObserver(); + observable.addObserver(o); + return toDisposable(() => { + observable.removeObserver(o); + }); +} + +class KeepAliveObserver implements IObserver { + beginUpdate(observable: IObservable): void { + // NO OP + } + + handleChange(observable: IObservable, change: TChange): void { + // NO OP + } + + endUpdate(observable: IObservable): void { + // NO OP + } +} + +export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { + let lastValue: T | undefined = undefined; + const observable = derived(name, reader => { + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return observable; +} + +export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { + let lastValue: T | undefined = undefined; + const counter = observableValue('derivedObservableWithWritableCache.counter', 0); + const observable = derived(name, reader => { + counter.read(reader); + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return Object.assign(observable, { + clearCache: (transaction: ITransaction) => { + lastValue = undefined; + counter.set(counter.get() + 1, transaction); + }, + }); +} diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 94346ca4f68..5fd52a6fa76 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -68,7 +68,6 @@ const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer' interface INavigator { userAgent: string; - language: string; maxTouchPoints?: number; } declare const navigator: INavigator; @@ -90,7 +89,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) { nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') ); - _locale = configuredLocale || navigator.language; + _locale = configuredLocale || LANGUAGE_DEFAULT; _language = _locale; } diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 5dcf73fc4a3..1ae8079810e 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -99,9 +99,7 @@ export interface IProductConfiguration { readonly enableTelemetry?: boolean; readonly openToWelcomeMainPage?: boolean; readonly aiConfig?: { - readonly asimovKey: string; readonly ariaKey: string; - readonly preferAria: boolean; }; readonly sendASmile?: { @@ -155,7 +153,7 @@ export interface IProductConfiguration { readonly 'configurationSync.store'?: ConfigurationSyncStore; - readonly 'sessionSync.store'?: Omit; + readonly 'editSessions.store'?: Omit; readonly darwinUniversalAssetId?: string; } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 28425d89a40..53796c58d8b 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -378,6 +378,10 @@ export function compareSubstringIgnoreCase(a: string, b: string, aStart: number return 0; } +export function isAsciiDigit(code: number): boolean { + return code >= CharCode.Digit0 && code <= CharCode.Digit9; +} + export function isLowerAsciiLetter(code: number): boolean { return code >= CharCode.a && code <= CharCode.z; } diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 4eded868127..8fd62606254 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -52,6 +52,7 @@ export function listProcesses(rootPid: number): Promise { const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/; const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/; const UTILITY_NETWORK_HINT = /--utility-sub-type=network/; + const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/; const WINDOWS_CRASH_REPORTER = /--crashes-directory/; const WINDOWS_PTY = /\\pipe\\winpty-control/; const WINDOWS_CONSOLE_HOST = /conhost\.exe/; @@ -93,6 +94,10 @@ export function listProcesses(rootPid: number): Promise { if (UTILITY_NETWORK_HINT.exec(cmd)) { return 'utility-network-service'; } + + if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) { + return 'extension-host'; + } } return matches[1]; } diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 9831dcde97c..bff8e41d8e5 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createHash } from 'crypto'; -import { createConnection, createServer, Server as NetServer, Socket } from 'net'; -import { tmpdir } from 'os'; +// import { createHash } from 'crypto'; +import type { Server as NetServer, Socket } from 'net'; +// import { tmpdir } from 'os'; +import type * as zlib from 'zlib'; import { VSBuffer } from 'vs/base/common/buffer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -15,7 +16,16 @@ import { Platform, platform } from 'vs/base/common/platform'; import { generateUuid } from 'vs/base/common/uuid'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; -import * as zlib from 'zlib'; + +// TODO@bpasero remove me once electron utility process has landed +function getNodeDependencies() { + return { + crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'), + zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'), + net: (require.__$__nodeRequire('net') as any) as typeof import('net'), + os: (require.__$__nodeRequire('os') as any) as typeof import('os') + }; +} export class NodeSocket implements ISocket { @@ -580,7 +590,7 @@ class ZlibInflateStream extends Disposable { options: zlib.ZlibOptions ) { super(); - this._zlibInflate = zlib.createInflateRaw(options); + this._zlibInflate = getNodeDependencies().zlib.createInflateRaw(options); this._zlibInflate.on('error', (err) => { this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); this._onError.fire(err); @@ -631,7 +641,7 @@ class ZlibDeflateStream extends Disposable { ) { super(); - this._zlibDeflate = zlib.createDeflateRaw({ + this._zlibDeflate = getNodeDependencies().zlib.createDeflateRaw({ windowBits: 15 }); this._zlibDeflate.on('error', (err) => { @@ -692,7 +702,8 @@ function unmask(buffer: VSBuffer, mask: number): void { // Read this before there's any chance it is overwritten // Related to https://github.com/microsoft/vscode/issues/30624 -export const XDG_RUNTIME_DIR = process.env['XDG_RUNTIME_DIR']; +// TODO@bpasero revert me once electron utility process has landed +export const XDG_RUNTIME_DIR = typeof process !== 'undefined' ? process.env['XDG_RUNTIME_DIR'] : undefined; const safeIpcPathLengths: { [platform: number]: number } = { [Platform.Linux]: 107, @@ -713,7 +724,7 @@ export function createRandomIPCHandle(): string { if (XDG_RUNTIME_DIR) { result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`); } else { - result = join(tmpdir(), `vscode-ipc-${randomSuffix}.sock`); + result = join(getNodeDependencies().os.tmpdir(), `vscode-ipc-${randomSuffix}.sock`); } // Validate length @@ -723,7 +734,7 @@ export function createRandomIPCHandle(): string { } export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string { - const scope = createHash('md5').update(directoryPath).digest('hex'); + const scope = getNodeDependencies().crypto.createHash('md5').update(directoryPath).digest('hex'); // Windows: use named pipe if (process.platform === 'win32') { @@ -785,7 +796,7 @@ export function serve(port: number): Promise; export function serve(namedPipe: string): Promise; export function serve(hook: any): Promise { return new Promise((c, e) => { - const server = createServer(); + const server = getNodeDependencies().net.createServer(); server.on('error', e); server.listen(hook, () => { @@ -800,7 +811,7 @@ export function connect(port: number, clientId: string): Promise; export function connect(namedPipe: string, clientId: string): Promise; export function connect(hook: any, clientId: string): Promise { return new Promise((c, e) => { - const socket = createConnection(hook, () => { + const socket = getNodeDependencies().net.createConnection(hook, () => { socket.removeListener('error', e); c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); }); diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index bdcb5114917..2ada8ddcc60 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -35,6 +35,7 @@ interface IListElement { readonly index: number; readonly item: IQuickPickItem; readonly saneLabel: string; + readonly saneSortLabel: string; readonly saneMeta?: string; readonly saneAriaLabel: string; readonly saneDescription?: string; @@ -52,6 +53,7 @@ class ListElement implements IListElement, IDisposable { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneSortLabel!: string; saneMeta!: string; saneAriaLabel!: string; saneDescription?: string; @@ -440,6 +442,7 @@ export class QuickInputList { if (item.type !== 'separator') { const previous = index && inputElements[index - 1]; const saneLabel = item.label && item.label.replace(/\r?\n/g, ' '); + const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim(); const saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' '); const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); @@ -454,6 +457,7 @@ export class QuickInputList { index, item, saneLabel, + saneSortLabel, saneMeta, saneAriaLabel, saneDescription, @@ -738,7 +742,7 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return 0; } - return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); + return compareAnything(elementA.saneSortLabel, elementB.saneSortLabel, lookFor); } class QuickInputAccessibilityProvider implements IListAccessibilityProvider { diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index 138d7486390..9dfe59a58fe 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -8,27 +8,6 @@ import * as collections from 'vs/base/common/collections'; suite('Collections', () => { - test('forEach', () => { - collections.forEach({}, () => assert(false)); - collections.forEach(Object.create(null), () => assert(false)); - - let count = 0; - collections.forEach({ toString: 123 }, () => count++); - assert.strictEqual(count, 1); - - count = 0; - const dict = Object.create(null); - dict['toString'] = 123; - collections.forEach(dict, () => count++); - assert.strictEqual(count, 1); - - collections.forEach(dict, () => false); - - // don't iterate over properties that are not on the object itself - const test = Object.create({ 'derived': true }); - collections.forEach(test, () => assert(false)); - }); - test('groupBy', () => { const group1 = 'a', group2 = 'b'; diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index a34566c10f1..39d878bce5d 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -8,7 +8,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event'; import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle'; -import { DisposableTracker } from 'vs/base/test/common/utils'; +import { observableValue, transaction } from 'vs/base/common/observable'; +import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; namespace Samples { @@ -624,6 +625,33 @@ suite('PausableEmitter', function () { }); }); +suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('fromObservable', function () { + + const obs = observableValue('test', 12); + const event = Event.fromObservable(obs); + + const values: number[] = []; + const d = event(n => { values.push(n); }); + + obs.set(3, undefined); + obs.set(13, undefined); + obs.set(3, undefined); + obs.set(33, undefined); + obs.set(1, undefined); + + transaction(tx => { + obs.set(334, tx); + obs.set(99, tx); + }); + + assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99])); + d.dispose(); + }); +}); + suite('Event utils', () => { suite('EventBufferer', () => { diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts new file mode 100644 index 00000000000..a3460dca22b --- /dev/null +++ b/src/vs/base/test/common/observable.test.ts @@ -0,0 +1,507 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; + +suite('observable integration', () => { + test('basic observable + autorun', () => { + const log = new Log(); + const observable = observableValue('MyObservableValue', 0); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${observable.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + transaction((tx) => { + observable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); + }); + + test('basic computed + autorun', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 0 = 1', + 'value: 1', + ]); + + observable2.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 1 = 2', + 'value: 2', + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 5 + 5 = 10', + 'value: 10', + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); + + transaction((tx) => { + observable1.set(-1, tx); + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 0 = -1', + 'computed is -1', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + + observable2.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 1 = 0', + 'value: 0', + ]); + }); + + test('topological order', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed1 = derived('computed1', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + const computed2 = derived('computed2', (reader) => { + const value1 = computed1.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + const computed3 = derived('computed3', (reader) => { + const value1 = computed2.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed3.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 0 + 0 = 0', + 'recompute2: 0 + 0 + 0 = 0', + 'recompute3: 0 + 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 + 0 = 1', + 'recompute2: 1 + 1 + 0 = 2', + 'recompute3: 2 + 1 + 0 = 3', + 'value: 3', + ]); + + transaction((tx) => { + observable1.set(2, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 2 + 0 = 2', + 'recompute2: 2 + 2 + 0 = 4', + 'computed2: 4', + ]); + + observable1.set(3, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 3 + 0 = 3', + 'recompute2: 3 + 3 + 0 = 6', + 'computed2: 6', + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute3: 6 + 3 + 0 = 9', + 'value: 9', + ]); + }); + + test('self-disposing autorun', () => { + const log = new Log(); + + const observable1 = new LoggingObservableValue('MyObservableValue1', 0, log); + const observable2 = new LoggingObservableValue('MyObservableValue2', 0, log); + const observable3 = new LoggingObservableValue('MyObservableValue3', 0, log); + + const d = autorun('autorun', (reader) => { + if (observable1.read(reader) >= 2) { + observable2.read(reader); + d.dispose(); + observable3.read(reader); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.firstObserverAdded', + 'MyObservableValue1.get', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.set (value 1)', + 'MyObservableValue1.get', + ]); + + observable1.set(2, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.set (value 2)', + 'MyObservableValue1.get', + 'MyObservableValue2.firstObserverAdded', + 'MyObservableValue2.get', + 'MyObservableValue1.lastObserverRemoved', + 'MyObservableValue2.lastObserverRemoved', + 'MyObservableValue3.get', + ]); + }); + + test('from event', () => { + const log = new Log(); + + let value = 0; + const eventEmitter = new Emitter(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + + const autorunDisposable = autorun('MyAutorun', (reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + value = 1; + eventEmitter.fire(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + // Maybe this scenario should not be supported. + + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const computed1 = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derived('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + }); +}); + +suite('observable details', () => { + test('1', () => { + const log = new Log(); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + const observable = new LoggingObservableValue('observable', 0, log); + const computed = derived('test', reader => { + if (shouldReadObservable.read(reader)) { + return observable.read(reader) * 2; + } + return 1; + }); + autorun('test', reader => { + const value = computed.read(reader); + log.log(`autorun: ${value}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.firstObserverAdded", "observable.get", "autorun: 0"])); + + transaction(tx => { + observable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + computed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.lastObserverRemoved"])); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); + }); +}); + +export class LoggingObserver implements IObserver { + private count = 0; + + constructor(public readonly debugName: string, private readonly log: Log) { + } + + beginUpdate(observable: IObservable): void { + this.count++; + this.log.log(`${this.debugName}.beginUpdate (count ${this.count})`); + } + handleChange(observable: IObservable, change: TChange): void { + this.log.log(`${this.debugName}.handleChange (count ${this.count})`); + } + endUpdate(observable: IObservable): void { + this.log.log(`${this.debugName}.endUpdate (count ${this.count})`); + this.count--; + } +} + +export class LoggingObservableValue + extends BaseObservable + implements ISettableObservable +{ + private value: T; + + constructor(public readonly debugName: string, initialValue: T, private readonly log: Log) { + super(); + this.value = initialValue; + } + + protected override onFirstObserverAdded(): void { + this.log.log(`${this.debugName}.firstObserverAdded`); + } + + protected override onLastObserverRemoved(): void { + this.log.log(`${this.debugName}.lastObserverRemoved`); + } + + public get(): T { + this.log.log(`${this.debugName}.get`); + return this.value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (this.value === value) { + return; + } + + if (!tx) { + transaction((tx) => { + this.set(value, tx, change); + }, () => `Setting ${this.debugName}`); + return; + } + + this.log.log(`${this.debugName}.set (value ${value})`); + + this.value = value; + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } + + override toString(): string { + return `${this.debugName}: ${this.value}`; + } +} + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts index a7ebe66abca..0116d6e8aa8 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts @@ -3,27 +3,186 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionGalleryService, IGlobalExtensionEnablementService, IServerExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, ServerDidUninstallExtensionEvent, ServerInstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { DidChangeProfilesEvent, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + +const uninstalOptions: UninstallOptions = { versionOnly: true, donotIncludePack: true, donotCheckDependents: true }; export class ExtensionsCleaner extends Disposable { constructor( - @IServerExtensionManagementService extensionManagementService: ExtensionManagementService, + @INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @IExtensionStorageService extensionStorageService: IExtensionStorageService, @IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService, + @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @ILogService logService: ILogService, ) { super(); - extensionManagementService.removeDeprecatedExtensions(); + + extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1); migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService); ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); + this._register(instantiationService.createInstance(ProfileExtensionsCleaner)); } + +} + +class ProfileExtensionsCleaner extends Disposable { + + private profileExtensionsLocations = new Map; + + private readonly profileModeDisposables = this._register(new MutableDisposable()); + + constructor( + @INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); + } + + private async onDidChangeProfiles({ added, removed, all }: DidChangeProfilesEvent): Promise { + try { + await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); + } catch (error) { + this.logService.error(error); + } + + if (all.length === 1) { + // Exit profile mode + this.profileModeDisposables.clear(); + // Listen for entering into profile mode + const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => { + disposable.dispose(); + this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); + })); + return; + } + + try { + if (added.length) { + await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); + // Enter profile mode + if (!this.profileModeDisposables.value) { + this.profileModeDisposables.value = new DisposableStore(); + this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear())); + this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); + this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); + this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); + await this.uninstallExtensionsNotInProfiles(); + } + } + } catch (error) { + this.logService.error(error); + } + } + + private async uninstallExtensionsNotInProfiles(): Promise { + const installed = await this.extensionManagementService.getAllUserInstalled(); + const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); + if (toUninstall.length) { + await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); + } + } + + private async onDidInstallExtensions(installedExtensions: readonly ServerInstallExtensionResult[]): Promise { + for (const { local, profileLocation } of installedExtensions) { + if (!local || !profileLocation) { + continue; + } + this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation); + } + } + + private async onDidUninstallExtension(e: ServerDidUninstallExtensionEvent): Promise { + if (!e.profileLocation || !e.version) { + return; + } + if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) { + await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]); + } + } + + private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise { + const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation); + for (const extension of extensions) { + this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation); + } + } + + private async removeExtensionsFromProfile(removedProfile: URI): Promise { + const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = []; + for (const key of [...this.profileExtensionsLocations.keys()]) { + if (!this.removeExtensionWithKey(key, removedProfile)) { + continue; + } + const extensionToRemove = this.fromKey(key); + if (extensionToRemove) { + extensionsToRemove.push(extensionToRemove); + } + } + if (extensionsToRemove.length) { + await this.uninstallExtensions(extensionsToRemove); + } + } + + private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void { + let locations = this.profileExtensionsLocations.get(key); + if (!locations) { + locations = []; + this.profileExtensionsLocations.set(key, locations); + } + locations.push(extensionsProfileLocation); + } + + private removeExtensionWithKey(key: string, profileLocation: URI): boolean { + const profiles = this.profileExtensionsLocations.get(key); + if (profiles) { + const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation)); + if (index > -1) { + profiles.splice(index, 1); + } + } + if (!profiles?.length) { + this.profileExtensionsLocations.delete(key); + return true; + } + return false; + } + + private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise { + const installed = await this.extensionManagementService.getAllUserInstalled(); + const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version))); + if (toUninstall.length) { + await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); + } + } + + private getKey(identifier: IExtensionIdentifier, version: string): string { + return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`; + } + + private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined { + const [id, version] = getIdAndVersion(key); + return version ? { identifier: { id }, version } : undefined; + } + } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 862a359dfd9..9cf505e2470 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -31,10 +31,10 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { SharedProcessEnvironmentService } from 'vs/platform/sharedProcess/node/sharedProcessEnvironmentService'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IDefaultExtensionsProfileInitService, IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { IFileService } from 'vs/platform/files/common/files'; @@ -64,7 +64,6 @@ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetry import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; @@ -106,6 +105,7 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender'; +import { DefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit'; class SharedProcessMain extends Disposable { @@ -281,16 +281,10 @@ class SharedProcessMain extends Disposable { const logAppender = new TelemetryLogAppender(loggerService, environmentService); appenders.push(logAppender); const { installSourcePath } = environmentService; - const internalTesting = configurationService.getValue('telemetry.internalTesting'); - if (internalTesting && productService.aiConfig?.ariaKey) { - const collectorAppender = new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig.ariaKey); + if (productService.aiConfig?.ariaKey) { + const collectorAppender = new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey); this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); - } else if (productService.aiConfig && productService.aiConfig.asimovKey) { - // Application Insights - const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey); - this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data - appenders.push(appInsightsAppender); } telemetryService = new TelemetryService({ @@ -315,7 +309,8 @@ class SharedProcessMain extends Disposable { // Extension Management services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(IDefaultExtensionsProfileInitService, new SyncDescriptor(DefaultExtensionsProfileInitService)); // Extension Gallery services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); @@ -426,6 +421,9 @@ class SharedProcessMain extends Disposable { // Worker const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService)); this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel); + + // Default Extensions Profile Init + this.server.registerChannel('IDefaultExtensionsProfileInitService', ProxyChannel.fromService(accessor.get(IDefaultExtensionsProfileInitService))); } private registerErrorHandler(logService: ILogService): void { diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html deleted file mode 100644 index 47066f520be..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js deleted file mode 100644 index 7ceef489fcc..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ /dev/null @@ -1,213 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// - -//@ts-check -(function () { - 'use strict'; - - const bootstrapWindow = bootstrapWindowLib(); - - // Add a perf entry right from the top - performance.mark('code/didStartRenderer'); - - // Load workbench main JS, CSS and NLS all in parallel. This is an - // optimization to prevent a waterfall of loading to happen, because - // we know for a fact that workbench.desktop.main will depend on - // the related CSS and NLS counterparts. - bootstrapWindow.load([ - 'vs/workbench/workbench.desktop.main', - 'vs/nls!vs/workbench/workbench.desktop.main', - 'vs/css!vs/workbench/workbench.desktop.main' - ], - function (_, configuration) { - - // Mark start of workbench - performance.mark('code/didLoadWorkbenchMain'); - - // @ts-ignore - return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); - }, - { - configureDeveloperSettings: function (windowConfig) { - return { - // disable automated devtools opening on error when running extension tests - // as this can lead to nondeterministic test execution (devtools steals focus) - forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', - // enable devtools keybindings in extension development window - forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, - removeDeveloperKeybindingsAfterLoad: true - }; - }, - canModifyDOM: function (windowConfig) { - showSplash(windowConfig); - }, - beforeLoaderConfig: function (loaderConfig) { - loaderConfig.recordStats = true; - }, - beforeRequire: function () { - performance.mark('code/willLoadWorkbenchMain'); - - // It looks like browsers only lazily enable - // the element when needed. Since we - // leverage canvas elements in our code in many - // locations, we try to help the browser to - // initialize canvas when it is idle, right - // before we wait for the scripts to be loaded. - // @ts-ignore - window.requestIdleCallback(() => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - canvas.remove(); - }, { timeout: 50 }); - } - } - ); - - //#region Helpers - - /** - * @typedef {import('../../../platform/window/common/window').INativeWindowConfiguration} INativeWindowConfiguration - * @typedef {import('../../../platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs - * - * @returns {{ - * load: ( - * modules: string[], - * resultCallback: (result, configuration: INativeWindowConfiguration & NativeParsedArgs) => unknown, - * options?: { - * configureDeveloperSettings?: (config: INativeWindowConfiguration & NativeParsedArgs) => { - * forceDisableShowDevtoolsOnError?: boolean, - * forceEnableDeveloperKeybindings?: boolean, - * disallowReloadKeybinding?: boolean, - * removeDeveloperKeybindingsAfterLoad?: boolean - * }, - * canModifyDOM?: (config: INativeWindowConfiguration & NativeParsedArgs) => void, - * beforeLoaderConfig?: (loaderConfig: object) => void, - * beforeRequire?: () => void - * } - * ) => Promise - * }} - */ - function bootstrapWindowLib() { - // @ts-ignore (defined in bootstrap-window.js) - return window.MonacoBootstrapWindow; - } - - /** - * @param {INativeWindowConfiguration & NativeParsedArgs} configuration - */ - function showSplash(configuration) { - performance.mark('code/willShowPartsSplash'); - - let data = configuration.partsSplash; - - if (data) { - // high contrast mode has been turned by the OS -> ignore stored colors and layouts - if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) { - if ((configuration.colorScheme.dark && data.baseTheme !== 'hc-black') || (!configuration.colorScheme.dark && data.baseTheme !== 'hc-light')) { - data = undefined; - } - } else if (configuration.autoDetectColorScheme) { - // OS color scheme is tracked and has changed - if ((configuration.colorScheme.dark && data.baseTheme !== 'vs-dark') || (!configuration.colorScheme.dark && data.baseTheme !== 'vs')) { - data = undefined; - } - } - } - - // developing an extension -> ignore stored layouts - if (data && configuration.extensionDevelopmentPath) { - data.layoutInfo = undefined; - } - - // minimal color configuration (works with or without persisted data) - let baseTheme, shellBackground, shellForeground; - if (data) { - baseTheme = data.baseTheme; - shellBackground = data.colorInfo.editorBackground; - shellForeground = data.colorInfo.foreground; - } else if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) { - if (configuration.colorScheme.dark) { - baseTheme = 'hc-black'; - shellBackground = '#000000'; - shellForeground = '#FFFFFF'; - } else { - baseTheme = 'hc-light'; - shellBackground = '#FFFFFF'; - shellForeground = '#000000'; - } - } else if (configuration.autoDetectColorScheme) { - if (configuration.colorScheme.dark) { - baseTheme = 'vs-dark'; - shellBackground = '#1E1E1E'; - shellForeground = '#CCCCCC'; - } else { - baseTheme = 'vs'; - shellBackground = '#FFFFFF'; - shellForeground = '#000000'; - } - } - - const style = document.createElement('style'); - style.className = 'initialShellColors'; - document.head.appendChild(style); - style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; - - // restore parts if possible (we might not always store layout info) - if (data?.layoutInfo) { - const { layoutInfo, colorInfo } = data; - - const splash = document.createElement('div'); - splash.id = 'monaco-parts-splash'; - splash.className = baseTheme; - - if (layoutInfo.windowBorder) { - splash.style.position = 'relative'; - splash.style.height = 'calc(100vh - 2px)'; - splash.style.width = 'calc(100vw - 2px)'; - splash.style.border = '1px solid var(--window-border-color)'; - splash.style.setProperty('--window-border-color', colorInfo.windowBorder); - - if (layoutInfo.windowBorderRadius) { - splash.style.borderRadius = layoutInfo.windowBorderRadius; - } - } - - // ensure there is enough space - layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth)); - - // part: title - const titleDiv = document.createElement('div'); - titleDiv.setAttribute('style', `position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;`); - splash.appendChild(titleDiv); - - // part: activity bar - const activityDiv = document.createElement('div'); - activityDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};`); - splash.appendChild(activityDiv); - - // part: side bar (only when opening workspace/folder) - // folder or workspace -> status bar color, sidebar - if (configuration.workspace) { - const sideDiv = document.createElement('div'); - sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`); - splash.appendChild(sideDiv); - } - - // part: statusbar - const statusDiv = document.createElement('div'); - statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`); - splash.appendChild(statusDiv); - - document.body.appendChild(splash); - } - - performance.mark('code/didShowPartsSplash'); - } - - //#endregion -}()); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7b912a581c3..14346728268 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -102,6 +102,8 @@ import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-m import { IPolicyService } from 'vs/platform/policy/common/policy'; import { PolicyChannel } from 'vs/platform/policy/common/policyIpc'; import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; +import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit'; /** * The main VS Code application. There will only ever be one instance, @@ -525,8 +527,8 @@ export class CodeApplication extends Disposable { // Services const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady); - // Setup Auth Handler - this._register(appInstantiationService.createInstance(ProxyAuthHandler)); + // Setup Handlers + this.setUpHandlers(appInstantiationService); // Init Channels appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); @@ -543,6 +545,14 @@ export class CodeApplication extends Disposable { } } + private setUpHandlers(instantiationService: IInstantiationService): void { + // Auth Handler + this._register(instantiationService.createInstance(ProxyAuthHandler)); + + // Default Extensions Profile Init Handler + this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler)); + } + private async resolveMachineId(): Promise { // We cache the machineId for faster lookups on startup @@ -679,6 +689,9 @@ export class CodeApplication extends Disposable { services.set(ITelemetryService, NullTelemetryService); } + // Default Extensions Profile Init + services.set(IDefaultExtensionsProfileInitService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('IDefaultExtensionsProfileInitService'))))); + // Init services that require it await backupMainService.initialize(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index b52ad7e31b2..1a4f72bd620 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -256,7 +256,7 @@ class CodeMain { configurationService.initialize() ]); - userDataProfilesMainService.setEnablement(configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + userDataProfilesMainService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); } private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 4037fbb3441..046cb3929ff 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -140,6 +140,7 @@ export class IssueReporter extends Disposable { this.handleExtensionData(configuration.data.enabledExtensions); this.updateExperimentsInfo(configuration.data.experiments); this.updateRestrictedMode(configuration.data.restrictedMode); + this.updateUnsupportedMode(configuration.data.isUnsupported); } render(): void { @@ -1154,6 +1155,10 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ restrictedMode }); } + private updateUnsupportedMode(isUnsupported: boolean) { + this.issueReporterModel.update({ isUnsupported }); + } + private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); const target = document.querySelector('.block-experiments .block-info'); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 37a1a40beb0..b0cc736e46e 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -33,6 +33,7 @@ export interface IssueReporterData { filterResultCount?: number; experimentInfo?: string; restrictedMode?: boolean; + isUnsupported?: boolean; } export class IssueReporterModel { @@ -61,14 +62,21 @@ export class IssueReporterModel { } serialize(): string { + const modes = []; + if (this._data.restrictedMode) { + modes.push('Restricted'); + } + if (this._data.isUnsupported) { + modes.push('Unsupported'); + } return ` -Issue Type: ${this.getIssueTypeTitle()} +Type: ${this.getIssueTypeTitle()} ${this._data.issueDescription} ${this.getExtensionVersion()} VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion} OS version: ${this._data.versionInfo && this._data.versionInfo.os} -Restricted Mode: ${this._data.restrictedMode ? 'Yes' : 'No'} +Modes:${modes.length ? ' ' + modes.join(', ') : ''} ${this.getRemoteOSes()} ${this.getInfos()} `; diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index e271fdf1bf4..0af36c6e1ac 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -16,10 +16,10 @@ // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because - // we know for a fact that workbench.desktop.sandbox.main will depend on + // we know for a fact that workbench.desktop.main will depend on // the related CSS and NLS counterparts. bootstrapWindow.load([ - 'vs/workbench/workbench.desktop.sandbox.main', + 'vs/workbench/workbench.desktop.main', 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], @@ -61,7 +61,7 @@ window.requestIdleCallback(() => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); + context?.clearRect(0, 0, canvas.width, canvas.height); canvas.remove(); }, { timeout: 50 }); } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 6ffc074614c..79c5b02e686 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -23,11 +23,11 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -53,7 +53,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; @@ -104,7 +104,7 @@ class CliMain extends Disposable { }); } - private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> { + private async initServices(): Promise<[IInstantiationService, OneDataSystemAppender[]]> { const services = new ServiceCollection(); // Product @@ -164,7 +164,7 @@ class CliMain extends Disposable { configurationService.initialize() ]); - userDataProfilesService.setEnablement(configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); @@ -178,7 +178,7 @@ class CliMain extends Disposable { // Extensions services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); @@ -186,10 +186,10 @@ class CliMain extends Disposable { services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); // Telemetry - const appenders: AppInsightsAppender[] = []; + const appenders: OneDataSystemAppender[] = []; if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.asimovKey) { - appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey)); + if (productService.aiConfig && productService.aiConfig.ariaKey) { + appenders.push(new OneDataSystemAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } const { installSourcePath } = environmentService; diff --git a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts index 87bca75fedf..e2280b2338e 100644 --- a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts +++ b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts @@ -27,13 +27,13 @@ suite('IssueReporter', () => { const issueReporterModel = new IssueReporterModel({}); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: Extensions: none `); @@ -58,13 +58,13 @@ Extensions: none }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes:
System Info @@ -102,13 +102,13 @@ Restricted Mode: No }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes:
System Info @@ -157,13 +157,13 @@ vsins829:30139715 }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes:
System Info @@ -214,13 +214,13 @@ Restricted Mode: No }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: Remote OS version: Linux x64 4.18.0
@@ -263,13 +263,13 @@ Remote OS version: Linux x64 4.18.0 }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes:
System Info @@ -287,6 +287,24 @@ Restricted Mode: No `); }); + test('should supply mode if applicable', () => { + const issueReporterModel = new IssueReporterModel({ + isUnsupported: true, + restrictedMode: true + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: Restricted, Unsupported + +Extensions: none +`); + }); test('should normalize GitHub urls', () => { [ 'https://github.com/repo', diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 49d6a503a84..42f1bc526a8 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { forEach } from 'vs/base/common/collections'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; export interface ISettingsReader { @@ -152,14 +151,14 @@ const suggestFilteredTypesMapping: Record = { registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => { if (value && typeof value === 'object') { - forEach(suggestFilteredTypesMapping, entry => { - const v = value[entry.key]; + for (const entry of Object.entries(suggestFilteredTypesMapping)) { + const v = value[entry[0]]; if (v === false) { - if (typeof read(`suggest.${entry.value}`) === 'undefined') { - write(`suggest.${entry.value}`, false); + if (typeof read(`suggest.${entry[1]}`) === 'undefined') { + write(`suggest.${entry[1]}`, false); } } - }); + } write('suggest.filteredTypes', undefined); } }); diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 45970c2a2b6..8ce775febd3 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -522,7 +522,7 @@ export class TextAreaHandler extends ViewPart { this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { - // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 500 for a better experience + // If a screen reader is attached and the default value is not set we should automatically increase the page size to 500 for a better experience this._accessibilityPageSize = 500; } else { this._accessibilityPageSize = accessibilityPageSize; diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts index 65d83ee29b8..de99d817fcd 100644 --- a/src/vs/editor/browser/dnd.ts +++ b/src/vs/editor/browser/dnd.ts @@ -41,8 +41,8 @@ const INTERNAL_DND_MIME_TYPES = Object.freeze([ DataTransfers.RESOURCES, ]); -export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent) { - if (dragEvent.dataTransfer && !dataTransfer.has(Mimes.uriList)) { +export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent, overwriteUriList = false) { + if (dragEvent.dataTransfer && (overwriteUriList || !dataTransfer.has(Mimes.uriList))) { const editorData = extractEditorsDropData(dragEvent) .filter(input => input.resource) .map(input => input.resource!.toString()); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index e620b237b81..f91574dc4cb 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -852,24 +852,29 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * All decorations added through this call will get the ownerId of this editor. - * @see {@link ITextModel.deltaDecorations} + * @deprecated */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; /** - * @internal + * Remove previously added decorations. */ - setDecorations(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; + removeDecorations(decorationIds: string[]): void; /** * @internal */ - setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void; + setDecorationsByType(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; /** * @internal */ - removeDecorations(decorationTypeKey: string): void; + setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void; + + /** + * @internal + */ + removeDecorationsByType(decorationTypeKey: string): void; /** * Get the layout info for the editor. diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index da58f07c2e7..761c4eee60f 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -161,7 +161,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC if (provider.refCount <= 0) { this._decorationOptionProviders.delete(key); provider.dispose(); - this.listCodeEditors().forEach((ed) => ed.removeDecorations(key)); + this.listCodeEditors().forEach((ed) => ed.removeDecorationsByType(key)); } } } diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 1fa168aedb1..1ec6fde3bfb 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, WorkspaceFileEdit, WorkspaceFileEditOptions, WorkspaceTextEdit } from 'vs/editor/common/languages'; +import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, IWorkspaceFileEdit, WorkspaceFileEditOptions, IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -15,49 +15,77 @@ import { CancellationToken } from 'vs/base/common/cancellation'; export const IBulkEditService = createDecorator('IWorkspaceEditService'); -function isWorkspaceFileEdit(thing: any): thing is WorkspaceFileEdit { - return isObject(thing) && (Boolean((thing).newUri) || Boolean((thing).oldUri)); -} - -function isWorkspaceTextEdit(thing: any): thing is WorkspaceTextEdit { - return isObject(thing) && URI.isUri((thing).resource) && isObject((thing).edit); -} - export class ResourceEdit { protected constructor(readonly metadata?: WorkspaceEditMetadata) { } static convert(edit: WorkspaceEdit): ResourceEdit[] { - return edit.edits.map(edit => { - if (isWorkspaceTextEdit(edit)) { - return new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata); + if (ResourceTextEdit.is(edit)) { + return ResourceTextEdit.lift(edit); } - if (isWorkspaceFileEdit(edit)) { - return new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata); + + if (ResourceFileEdit.is(edit)) { + return ResourceFileEdit.lift(edit); } throw new Error('Unsupported edit'); }); } } -export class ResourceTextEdit extends ResourceEdit { +export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit { + + static is(candidate: any): candidate is IWorkspaceTextEdit { + if (candidate instanceof ResourceTextEdit) { + return true; + } + return isObject(candidate) + && URI.isUri((candidate).resource) + && isObject((candidate).textEdit); + } + + static lift(edit: IWorkspaceTextEdit): ResourceTextEdit { + if (edit instanceof ResourceTextEdit) { + return edit; + } else { + return new ResourceTextEdit(edit.resource, edit.textEdit, edit.versionId, edit.metadata); + } + } + constructor( readonly resource: URI, readonly textEdit: TextEdit & { insertAsSnippet?: boolean }, - readonly versionId?: number, + readonly versionId: number | undefined = undefined, metadata?: WorkspaceEditMetadata, ) { super(metadata); } } -export class ResourceFileEdit extends ResourceEdit { +export class ResourceFileEdit extends ResourceEdit implements IWorkspaceFileEdit { + + static is(candidate: any): candidate is IWorkspaceFileEdit { + if (candidate instanceof ResourceFileEdit) { + return true; + } else { + return isObject(candidate) + && (Boolean((candidate).newResource) || Boolean((candidate).oldResource)); + } + } + + static lift(edit: IWorkspaceFileEdit): ResourceFileEdit { + if (edit instanceof ResourceFileEdit) { + return edit; + } else { + return new ResourceFileEdit(edit.oldResource, edit.newResource, edit.options, edit.metadata); + } + } + constructor( readonly oldResource: URI | undefined, readonly newResource: URI | undefined, - readonly options?: WorkspaceFileEditOptions, + readonly options: WorkspaceFileEditOptions = {}, metadata?: WorkspaceEditMetadata ) { super(metadata); diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 5d09d9920fa..d362663580b 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -172,7 +172,13 @@ export class ViewCursor { } const range = firstVisibleRangeForCharacter.ranges[0]; - const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width; + const width = ( + nextGrapheme === '\t' + ? this._typicalHalfwidthCharacterWidth + : (range.width < 1 + ? this._typicalHalfwidthCharacterWidth + : range.width) + ); let textContentClassName = ''; if (this._cursorStyle === TextEditorCursorStyle.Block) { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 75810f5c95e..f4e8c0d10db 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -267,7 +267,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private _bannerDomNode: HTMLElement | null = null; - private _dropIntoEditorDecorationIds: string[] = []; + private _dropIntoEditorDecorations: EditorDecorationsCollection = this.createDecorationsCollection(); constructor( domElement: HTMLElement, @@ -564,27 +564,43 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.viewModel.viewLayout.getWhitespaces(); } - private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number): number { + private static _getVerticalOffsetAfterPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean): number { const modelPosition = modelData.model.validatePosition({ lineNumber: modelLineNumber, column: modelColumn }); const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); - return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); + return modelData.viewModel.viewLayout.getVerticalOffsetAfterLineNumber(viewPosition.lineNumber, includeViewZones); } - public getTopForLineNumber(lineNumber: number): number { + public getTopForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1, includeViewZones); } public getTopForPosition(lineNumber: number, column: number): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column, false); + } + + private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean = false): number { + const modelPosition = modelData.model.validatePosition({ + lineNumber: modelLineNumber, + column: modelColumn + }); + const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); + return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber, includeViewZones); + } + + public getBottomForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + if (!this._modelData) { + return -1; + } + return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, 1, includeViewZones); } public setHiddenAreas(ranges: IRange[]): void { @@ -1286,6 +1302,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(this._configuration.options)); } + /** + * @deprecated + */ public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { if (!this._modelData) { return []; @@ -1298,7 +1317,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.deltaDecorations(oldDecorations, newDecorations, this._id); } - public setDecorations(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { + public removeDecorations(decorationIds: string[]): void { + if (!this._modelData || decorationIds.length === 0) { + return; + } + + this._modelData.model.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations(decorationIds, []); + }); + } + + public setDecorationsByType(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { const newDecorationsSubTypes: { [key: string]: boolean } = {}; const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1340,7 +1369,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void { + public setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void { // remove decoration sub types that are no longer used, deregister decoration type if necessary const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1360,7 +1389,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public removeDecorations(decorationTypeKey: string): void { + public removeDecorationsByType(decorationTypeKey: string): void { // remove decorations for type and sub type const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; if (oldDecorationsIds) { @@ -1834,12 +1863,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE options: CodeEditorWidget.dropIntoEditorDecorationOptions }]; - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, newDecorations); + this._dropIntoEditorDecorations.set(newDecorations); this.revealPosition(position, editorCommon.ScrollType.Immediate); } private removeDropIndicator(): void { - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, []); + this._dropIntoEditorDecorations.clear(); } } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 59cd4e9918a..ee1a5a107b9 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -115,7 +115,9 @@ class VisualEditorState { this._zonesMap = {}; // (2) Model decorations - this._decorations = editor.deltaDecorations(this._decorations, []); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, []); + }); } public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler | null, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void { @@ -153,7 +155,9 @@ class VisualEditorState { scrollState?.restore(editor); // decorations - this._decorations = editor.deltaDecorations(this._decorations, newDecorations.decorations); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, newDecorations.decorations); + }); // overview ruler overviewRuler?.setZones(newDecorations.overviewZones); @@ -260,6 +264,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._options = validateDiffEditorOptions(options, { enableSplitViewResizing: true, renderSideBySide: true, + renderMarginRevertIcon: true, maxComputationTime: 5000, maxFileSize: 50, ignoreTrimWhitespace: true, @@ -646,7 +651,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE range, text: '', }]); - } else if (change.modifiedEndLineNumber === 0 && originalContent) { + } else if (change.modifiedEndLineNumber === 0 && originalContent !== null) { // Delete change. // To revert: insert the old content and a linebreak. @@ -745,7 +750,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const changed = changedDiffEditorOptions(this._options, newOptions); this._options = newOptions; - const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators); + const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon); const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize)); if (beginUpdateDecorations) { @@ -1143,7 +1148,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const foreignOriginal = this._originalEditorState.getForeignViewZones(this._originalEditor.getWhitespaces()); const foreignModified = this._modifiedEditorState.getForeignViewZones(this._modifiedEditor.getWhitespaces()); - const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._options.ignoreTrimWhitespace, this._options.renderIndicators, foreignOriginal, foreignModified); + const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._options.ignoreTrimWhitespace, this._options.renderIndicators, this._options.renderMarginRevertIcon, foreignOriginal, foreignModified); try { this._currentlyChangingViewZones = true; @@ -1459,7 +1464,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { return hasChanges; } - public getEditorsDiffDecorations(lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[]): IEditorsDiffDecorationsWithZones { + public getEditorsDiffDecorations(lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[]): IEditorsDiffDecorationsWithZones { // Get view zones modifiedWhitespaces = modifiedWhitespaces.sort((a, b) => { return a.afterLineNumber - b.afterLineNumber; @@ -1471,7 +1476,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { // Get decorations & overview ruler zones const originalDecorations = this._getOriginalEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators); - const modifiedDecorations = this._getModifiedEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators); + const modifiedDecorations = this._getModifiedEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators, renderMarginRevertIcon); return { original: { @@ -1489,7 +1494,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { protected abstract _getViewZones(lineChanges: ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones; protected abstract _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; - protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; + protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations; public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; public abstract layout(): number; @@ -2031,7 +2036,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti return result; } - protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations { const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); @@ -2046,15 +2051,17 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti for (const lineChange of lineChanges) { // Arrows for reverting changes. - if (lineChange.modifiedEndLineNumber > 0) { - result.decorations.push({ - range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber, 1), - options: DECORATIONS.arrowRevertChange - }); - } else { - const viewZone = zones.modified.find(z => z.afterLineNumber === lineChange.modifiedStartLineNumber); - if (viewZone) { - viewZone.marginDomNode = createViewZoneMarginArrow(); + if (renderMarginRevertIcon) { + if (lineChange.modifiedEndLineNumber > 0) { + result.decorations.push({ + range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber, 1), + options: DECORATIONS.arrowRevertChange + }); + } else { + const viewZone = zones.modified.find(z => z.afterLineNumber === lineChange.modifiedStartLineNumber); + if (viewZone) { + viewZone.marginDomNode = createViewZoneMarginArrow(); + } } } @@ -2222,7 +2229,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle { return result; } - protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations { const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); @@ -2641,6 +2648,7 @@ function validateDiffEditorOptions(options: Readonly, defaul return { enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing), renderSideBySide: validateBooleanOption(options.renderSideBySide, defaults.renderSideBySide), + renderMarginRevertIcon: validateBooleanOption(options.renderMarginRevertIcon, defaults.renderMarginRevertIcon), maxComputationTime: clampedInt(options.maxComputationTime, defaults.maxComputationTime, 0, Constants.MAX_SAFE_SMALL_INTEGER), maxFileSize: clampedInt(options.maxFileSize, defaults.maxFileSize, 0, Constants.MAX_SAFE_SMALL_INTEGER), ignoreTrimWhitespace: validateBooleanOption(options.ignoreTrimWhitespace, defaults.ignoreTrimWhitespace), @@ -2656,6 +2664,7 @@ function changedDiffEditorOptions(a: ValidDiffEditorBaseOptions, b: ValidDiffEdi return { enableSplitViewResizing: (a.enableSplitViewResizing !== b.enableSplitViewResizing), renderSideBySide: (a.renderSideBySide !== b.renderSideBySide), + renderMarginRevertIcon: (a.renderMarginRevertIcon !== b.renderMarginRevertIcon), maxComputationTime: (a.maxComputationTime !== b.maxComputationTime), maxFileSize: (a.maxFileSize !== b.maxFileSize), ignoreTrimWhitespace: (a.ignoreTrimWhitespace !== b.ignoreTrimWhitespace), diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index 650f20b7047..50f727fa7dc 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -146,6 +146,11 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('sideBySide', "Controls whether the diff editor shows the diff side by side or inline.") }, + 'diffEditor.renderMarginRevertIcon': { + type: 'boolean', + default: true, + description: nls.localize('renderMarginRevertIcon', "When enabled, the diff editor shows arrows in its glyph margin to revert changes.") + }, 'diffEditor.ignoreTrimWhitespace': { type: 'boolean', default: true, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3cd0cc5ab43..7292a7e132b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -563,7 +563,7 @@ export interface IEditorOptions { * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. * Defaults to 'mouseover'. */ - showFoldingControls?: 'always' | 'mouseover'; + showFoldingControls?: 'always' | 'never' | 'mouseover'; /** * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. @@ -705,6 +705,11 @@ export interface IDiffEditorBaseOptions { * Defaults to true. */ renderIndicators?: boolean; + /** + * Shows icons in the glyph margin to revert changes. + * Default to true. + */ + renderMarginRevertIcon?: boolean; /** * Original model should be editable? * Defaults to false. @@ -2326,6 +2331,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption; + edits: Array; } export interface Rejection { diff --git a/src/vs/editor/common/languages/linkComputer.ts b/src/vs/editor/common/languages/linkComputer.ts index 0ee39a20229..e7111b3ba3d 100644 --- a/src/vs/editor/common/languages/linkComputer.ts +++ b/src/vs/editor/common/languages/linkComputer.ts @@ -258,15 +258,15 @@ export class LinkComputer { case CharCode.CloseCurlyBrace: chClass = (hasOpenCurlyBracket ? CharacterClass.None : CharacterClass.ForceTermination); break; - /* The following three rules make it that ' or " or ` are allowed inside links if the link began with a different one */ + /* The following three rules make it that ' or " or ` are allowed inside links if the link didn't begin with them */ case CharCode.SingleQuote: - chClass = (linkBeginChCode === CharCode.DoubleQuote || linkBeginChCode === CharCode.BackTick) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.SingleQuote ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.DoubleQuote: - chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.BackTick) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.DoubleQuote ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.BackTick: - chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.DoubleQuote) ? CharacterClass.None : CharacterClass.ForceTermination; + chClass = (linkBeginChCode === CharCode.BackTick ? CharacterClass.ForceTermination : CharacterClass.None); break; case CharCode.Asterisk: // `*` terminates a link if the link began with `*` diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index cc9b43f6d24..d68936dbda4 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -11,7 +11,7 @@ import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { DiffComputer, IChange, IDiffComputationResult } from 'vs/editor/common/diff/diffComputer'; -import { EndOfLineSequence } from 'vs/editor/common/model'; +import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; import { IMirrorTextModel, IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel'; import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/editor/common/core/wordHelper'; import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; @@ -388,8 +388,12 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { return null; } - const originalLines = original.getLinesContent(); - const modifiedLines = modified.getLinesContent(); + return EditorSimpleWorker.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime); + } + + public static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, ignoreTrimWhitespace: boolean, maxComputationTime: number): IDiffComputationResult | null { + const originalLines = originalTextModel.getLinesContent(); + const modifiedLines = modifiedTextModel.getLinesContent(); const diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldComputeCharChanges: true, shouldPostProcessCharChanges: true, @@ -399,7 +403,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { }); const diffResult = diffComputer.computeDiff(); - const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(original, modified)); + const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel)); return { quitEarly: diffResult.quitEarly, identical: identical, @@ -407,7 +411,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { }; } - private _modelsAreIdentical(original: ICommonModel, modified: ICommonModel): boolean { + private static _modelsAreIdentical(original: ICommonModel | ITextModel, modified: ICommonModel | ITextModel): boolean { const originalLineCount = original.getLineCount(); const modifiedLineCount = modified.getLineCount(); if (originalLineCount !== modifiedLineCount) { diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 01e2d26cf5a..7bb55aeef6e 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -482,7 +482,7 @@ export class LinesLayout { * @param lineNumber The line number * @return The sum of heights for all objects above `lineNumber`. */ - public getVerticalOffsetForLineNumber(lineNumber: number): number { + public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones = false): number { this._checkPendingChanges(); lineNumber = lineNumber | 0; @@ -493,11 +493,25 @@ export class LinesLayout { previousLinesHeight = 0; } - const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber); + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber - (includeViewZones ? 1 : 0)); return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; } + /** + * Get the vertical offset (the sum of heights for all objects above) a certain line number. + * + * @param lineNumber The line number + * @return The sum of heights for all objects above `lineNumber`. + */ + public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones = false): number { + this._checkPendingChanges(); + lineNumber = lineNumber | 0; + const previousLinesHeight = this._lineHeight * lineNumber; + const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber + (includeViewZones ? 1 : 0)); + return previousLinesHeight + previousWhitespacesHeight + this._paddingTop; + } + /** * Returns if there is any whitespace in the document. */ diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index fa5013cac62..0acbe29204f 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -362,8 +362,11 @@ export class ViewLayout extends Disposable implements IViewLayout { } return hadAChange; } - public getVerticalOffsetForLineNumber(lineNumber: number): number { - return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber); + public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones); + } + public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones); } public isAfterLines(verticalOffset: number): boolean { return this._linesLayout.isAfterLines(verticalOffset); diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 19fea69d49c..e582f7dc719 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -133,7 +133,7 @@ export interface IEditorWhitespace { } /** - * An accessor that allows for whtiespace to be added, removed or changed in bulk. + * An accessor that allows for whitespace to be added, removed or changed in bulk. */ export interface IWhitespaceChangeAccessor { insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string; diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index e9a71900cf1..488fc8a6056 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -107,6 +107,7 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { submenu: MenuId.MenubarCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: '2_ccp', order: 3 }); MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }); +MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1 }); export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts index d1be6b9d645..93005d11387 100644 --- a/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts @@ -73,7 +73,7 @@ suite('CodeAction', () => { bcd: { diagnostics: [], edit: new class implements languages.WorkspaceEdit { - edits!: languages.WorkspaceTextEdit[]; + edits!: languages.IWorkspaceTextEdit[]; }, title: 'abc' } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 02ba389e69f..d4cd26914b1 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -172,10 +172,12 @@ export class ColorDetector extends Disposable implements IEditorContribution { options: ModelDecorationOptions.EMPTY })); - this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, decorations); + this._editor.changeDecorations((changeAccessor) => { + this._decorationsIds = changeAccessor.deltaDecorations(this._decorationsIds, decorations); - this._colorDatas = new Map(); - this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i])); + this._colorDatas = new Map(); + this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i])); + }); } private _colorDecorationClassRefs = this._register(new DisposableStore()); @@ -219,7 +221,8 @@ export class ColorDetector extends Disposable implements IEditorContribution { } private removeAllDecorations(): void { - this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, []); + this._editor.removeDecorations(this._decorationsIds); + this._decorationsIds = []; this._colorDecoratorIds.clear(); this._colorDecorationClassRefs.clear(); } diff --git a/src/vs/editor/contrib/find/browser/findDecorations.ts b/src/vs/editor/contrib/find/browser/findDecorations.ts index 16d9a207b03..4d1c39cd979 100644 --- a/src/vs/editor/contrib/find/browser/findDecorations.ts +++ b/src/vs/editor/contrib/find/browser/findDecorations.ts @@ -33,7 +33,7 @@ export class FindDecorations implements IDisposable { } public dispose(): void { - this._editor.deltaDecorations(this._allDecorations(), []); + this._editor.removeDecorations(this._allDecorations()); this._decorations = []; this._overviewRulerApproximateDecorations = []; diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index 391249b2436..c38e6071eef 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -127,7 +127,7 @@ export class FoldingController extends Disposable implements IEditorContribution this.mouseDownInfo = null; this.foldingDecorationProvider = new FoldingDecorationProvider(editor); - this.foldingDecorationProvider.autoHideFoldingControls = options.get(EditorOption.showFoldingControls) === 'mouseover'; + this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls); this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight); this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService); this.foldingEnabled.set(this._isEnabled); @@ -159,7 +159,7 @@ export class FoldingController extends Disposable implements IEditorContribution } if (e.hasChanged(EditorOption.showFoldingControls) || e.hasChanged(EditorOption.foldingHighlight)) { const options = this.editor.getOptions(); - this.foldingDecorationProvider.autoHideFoldingControls = options.get(EditorOption.showFoldingControls) === 'mouseover'; + this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls); this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight); this.triggerFoldingModelChanged(); } diff --git a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts index a171ad7059d..348b1e7da1a 100644 --- a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts @@ -5,7 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IDecorationProvider } from 'vs/editor/contrib/folding/browser/foldingModel'; import { localize } from 'vs/nls'; @@ -52,7 +52,7 @@ export class FoldingDecorationProvider implements IDecorationProvider { stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }); - public autoHideFoldingControls: boolean = true; + public showFoldingControls: 'always' | 'never' | 'mouseover' = 'mouseover'; public showFoldingHighlights: boolean = true; @@ -60,23 +60,23 @@ export class FoldingDecorationProvider implements IDecorationProvider { } getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions { - if (isHidden) { + if (isHidden || this.showFoldingControls === 'never') { return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION; } if (isCollapsed) { return this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION; - } else if (this.autoHideFoldingControls) { + } else if (this.showFoldingControls === 'mouseover') { return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION; } else { return FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION; } } - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { - return this.editor.deltaDecorations(oldDecorations, newDecorations); - } - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T { return this.editor.changeDecorations(callback); } + + removeDecorations(decorationIds: string[]): void { + this.editor.removeDecorations(decorationIds); + } } diff --git a/src/vs/editor/contrib/folding/browser/foldingModel.ts b/src/vs/editor/contrib/folding/browser/foldingModel.ts index c4c9f791b7c..ba52acfdea8 100644 --- a/src/vs/editor/contrib/folding/browser/foldingModel.ts +++ b/src/vs/editor/contrib/folding/browser/foldingModel.ts @@ -9,8 +9,8 @@ import { FoldingRegion, FoldingRegions, ILineRange } from './foldingRanges'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean, isHidden: boolean): IModelDecorationOptions; - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null; + removeDecorations(decorationIds: string[]): void; } export interface FoldingModelChangeEvent { @@ -162,7 +162,9 @@ export class FoldingModel { k++; } - this._editorDecorationIds = this._decorationProvider.deltaDecorations(this._editorDecorationIds, newEditorDecorations); + this._decorationProvider.changeDecorations((changeAccessor) => { + this._editorDecorationIds = changeAccessor.deltaDecorations(this._editorDecorationIds, newEditorDecorations); + }); this._regions = newRegions; this._isInitialized = true; this._updateEventEmitter.fire({ model: this }); @@ -207,7 +209,7 @@ export class FoldingModel { } public dispose() { - this._decorationProvider.deltaDecorations(this._editorDecorationIds, []); + this._decorationProvider.removeDecorations(this._editorDecorationIds); } getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] { diff --git a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index f57e265bf09..9f2e3a0344c 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -7,7 +7,7 @@ import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { FoldingModel, getNextFoldLine, getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp } from 'vs/editor/contrib/folding/browser/foldingModel'; import { FoldingRegion } from 'vs/editor/contrib/folding/browser/foldingRanges'; @@ -59,14 +59,16 @@ export class TestDecorationProvider { return TestDecorationProvider.expandedDecoration; } - deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { - return this.model.deltaDecorations(oldDecorations, newDecorations); - } - changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): (T | null) { return this.model.changeDecorations(callback); } + removeDecorations(decorationIds: string[]): void { + this.model.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations(decorationIds, []); + }); + } + getDecorations(): ExpectedDecoration[] { const decorations = this.model.getAllDecorations(); const res: ExpectedDecoration[] = []; diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index fd70d871ad7..ee7ef53aa64 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -100,10 +100,12 @@ class DecorationsManager implements IDisposable { newDecorationsActualIndex.push(i); } - const decorations = this._editor.deltaDecorations([], newDecorations); - for (let i = 0; i < decorations.length; i++) { - this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); - } + this._editor.changeDecorations((changeAccessor) => { + const decorations = changeAccessor.deltaDecorations([], newDecorations); + for (let i = 0; i < decorations.length; i++) { + this._decorations.set(decorations[i], reference.children[newDecorationsActualIndex[i]]); + } + }); } private _onDecorationChanged(): void { @@ -151,11 +153,11 @@ class DecorationsManager implements IDisposable { for (let i = 0, len = toRemove.length; i < len; i++) { this._decorations.delete(toRemove[i]); } - this._editor.deltaDecorations(toRemove, []); + this._editor.removeDecorations(toRemove); } removeDecorations(): void { - this._editor.deltaDecorations([...this._decorations.keys()], []); + this._editor.removeDecorations([...this._decorations.keys()]); this._decorations.clear(); } } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 30204ef7f6a..93941e6172c 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -415,7 +415,6 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { this._hover.contentsDomNode.style.paddingBottom = ''; this._updateFont(); - this._editor.layoutContentWidget(this); this.onContentsChanged(); // Simply force a synchronous render on the editor @@ -424,7 +423,6 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor - this._editor.layoutContentWidget(this); this.onContentsChanged(); if (visibleData.stoleFocus) { @@ -447,6 +445,7 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { } public onContentsChanged(): void { + this._editor.layoutContentWidget(this); this._hover.onContentsChanged(); const scrollDimensions = this._hover.scrollbar.getScrollDimensions(); diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts index 30c8a909369..0e59778b739 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsController.ts @@ -468,7 +468,7 @@ export class InlayHintsController implements IEditorContribution { // - const { fontSize, fontFamily, padding } = this._getLayoutInfo(); + const { fontSize, fontFamily, padding, isUniform } = this._getLayoutInfo(); const fontFamilyVar = '--code-editorInlayHintsFontFamily'; this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily); @@ -493,7 +493,7 @@ export class InlayHintsController implements IEditorContribution { const cssProperties: CssProperties = { fontSize: `${fontSize}px`, fontFamily: `var(${fontFamilyVar}), ${EDITOR_FONT_DEFAULTS.fontFamily}`, - verticalAlign: 'middle', + verticalAlign: isUniform ? 'baseline' : 'middle', }; if (isNonEmptyArray(item.hint.textEdits)) { @@ -587,17 +587,27 @@ export class InlayHintsController implements IEditorContribution { private _getLayoutInfo() { const options = this._editor.getOption(EditorOption.inlayHints); + const padding = options.padding; + const editorFontSize = this._editor.getOption(EditorOption.fontSize); + const editorFontFamily = this._editor.getOption(EditorOption.fontFamily); + let fontSize = options.fontSize; if (!fontSize || fontSize < 5 || fontSize > editorFontSize) { fontSize = editorFontSize; } - const fontFamily = options.fontFamily || this._editor.getOption(EditorOption.fontFamily); - return { fontSize, fontFamily, padding: options.padding }; + + const fontFamily = options.fontFamily || editorFontFamily; + + const isUniform = !padding + && fontFamily === editorFontFamily + && fontSize === editorFontSize; + + return { fontSize, fontFamily, padding, isUniform }; } private _removeAllDecorations(): void { - this._editor.deltaDecorations(Array.from(this._decorationsMetadata.keys()), []); + this._editor.removeDecorations(Array.from(this._decorationsMetadata.keys())); for (const obj of this._decorationsMetadata.values()) { obj.classNameRef.dispose(); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 13a8e3b840c..b60a6dec142 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -47,7 +47,7 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge private readonly debounceValue = this.debounceService.for( this.languageFeaturesService.inlineCompletionsProvider, 'InlineCompletionsDebounce', - { min: 50, max: 200 } + { min: 50, max: 50 } ); constructor( @@ -577,18 +577,21 @@ export class SynchronizedInlineCompletionsCache extends Disposable { ) { super(); - const decorationIds = editor.deltaDecorations( - [], - completionsSource.items.map(i => ({ - range: i.range, - options: { - description: 'inline-completion-tracking-range' - }, - })) - ); + const decorationIds = editor.changeDecorations((changeAccessor) => { + return changeAccessor.deltaDecorations( + [], + completionsSource.items.map(i => ({ + range: i.range, + options: { + description: 'inline-completion-tracking-range' + }, + })) + ); + }); + this._register(toDisposable(() => { this.isDisposing = true; - editor.deltaDecorations(decorationIds, []); + editor.removeDecorations(decorationIds); })); this.completions = completionsSource.items.map((c, idx) => new CachedInlineCompletion(c, decorationIds[idx])); diff --git a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts index d144f3e7e0f..d9091fdea2d 100644 --- a/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts +++ b/src/vs/editor/contrib/inlineCompletions/test/browser/inlineCompletionsProvider.test.ts @@ -524,7 +524,9 @@ suite('Inline Completions', () => { context.cursorDown(); context.keyboardType('hello'); - await timeout(100); + await timeout(40); + + assert.deepStrictEqual(provider.getAndClearCallHistory(), []); // Update ghost text context.keyboardType('w'); diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index 89493d5578e..ce0ebbc54af 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -1162,6 +1162,49 @@ export class SnakeCaseAction extends AbstractCaseAction { } } +export class KebabCaseAction extends AbstractCaseAction { + + public static isSupported(): boolean { + const areAllRegexpsSupported = [ + this.caseBoundary, + this.singleLetters, + this.underscoreBoundary, + ].every((regexp) => regexp.isSupported()); + + return areAllRegexpsSupported; + } + + private static caseBoundary = new BackwardsCompatibleRegExp('(\\p{Ll})(\\p{Lu})', 'gmu'); + private static singleLetters = new BackwardsCompatibleRegExp('(\\p{Lu}|\\p{N})(\\p{Lu}\\p{Ll})', 'gmu'); + private static underscoreBoundary = new BackwardsCompatibleRegExp('(\\S)(_)(\\S)', 'gm'); + + constructor() { + super({ + id: 'editor.action.transformToKebabcase', + label: nls.localize('editor.transformToKebabcase', 'Transform to Kebab Case'), + alias: 'Transform to Kebab Case', + precondition: EditorContextKeys.writable + }); + } + + protected _modifyText(text: string, _: string): string { + const caseBoundary = KebabCaseAction.caseBoundary.get(); + const singleLetters = KebabCaseAction.singleLetters.get(); + const underscoreBoundary = KebabCaseAction.underscoreBoundary.get(); + + if (!caseBoundary || !singleLetters || !underscoreBoundary) { + // one or more regexps aren't supported + return text; + } + + return text + .replace(underscoreBoundary, '$1-$3') + .replace(caseBoundary, '$1-$2') + .replace(singleLetters, '$1-$2') + .toLocaleLowerCase(); + } +} + registerEditorAction(CopyLinesUpAction); registerEditorAction(CopyLinesDownAction); registerEditorAction(DuplicateSelectionAction); @@ -1189,3 +1232,7 @@ if (SnakeCaseAction.caseBoundary.isSupported() && SnakeCaseAction.singleLetters. if (TitleCaseAction.titleBoundary.isSupported()) { registerEditorAction(TitleCaseAction); } + +if (KebabCaseAction.isSupported()) { + registerEditorAction(KebabCaseAction); +} diff --git a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts index c8a2cf809c8..755e6c657a7 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -11,7 +11,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; +import { DeleteAllLeftAction, DeleteAllRightAction, DeleteDuplicateLinesAction, DeleteLinesAction, IndentLinesAction, InsertLineAfterAction, InsertLineBeforeAction, JoinLinesAction, KebabCaseAction, LowerCaseAction, SnakeCaseAction, SortLinesAscendingAction, SortLinesDescendingAction, TitleCaseAction, TransposeAction, UpperCaseAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; @@ -814,6 +814,80 @@ suite('Editor Contrib - Line Operations', () => { assertSelection(editor, new Selection(2, 2, 2, 2)); } ); + + withTestCodeEditor( + [ + 'hello world', + 'öçşğü', + 'parseHTMLString', + 'getElementById', + 'PascalCase', + 'öçşÖÇŞğüĞÜ', + 'audioConverter.convertM4AToMP3();', + 'Capital_Snake_Case', + 'parseHTML4String', + '_accessor: ServicesAccessor', + 'Kebab-Case', + ], {}, (editor) => { + const model = editor.getModel()!; + const kebabCaseAction = new KebabCaseAction(); + + editor.setSelection(new Selection(1, 1, 1, 12)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(1), 'hello world'); + assertSelection(editor, new Selection(1, 1, 1, 12)); + + editor.setSelection(new Selection(2, 1, 2, 6)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(2), 'öçşğü'); + assertSelection(editor, new Selection(2, 1, 2, 6)); + + editor.setSelection(new Selection(3, 1, 3, 16)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(3), 'parse-html-string'); + assertSelection(editor, new Selection(3, 1, 3, 18)); + + editor.setSelection(new Selection(4, 1, 4, 15)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(4), 'get-element-by-id'); + assertSelection(editor, new Selection(4, 1, 4, 18)); + + editor.setSelection(new Selection(5, 1, 5, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(5), 'pascal-case'); + assertSelection(editor, new Selection(5, 1, 5, 12)); + + editor.setSelection(new Selection(6, 1, 6, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(6), 'öçş-öç-şğü-ğü'); + assertSelection(editor, new Selection(6, 1, 6, 14)); + + editor.setSelection(new Selection(7, 1, 7, 34)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(7), 'audio-converter.convert-m4a-to-mp3();'); + assertSelection(editor, new Selection(7, 1, 7, 38)); + + editor.setSelection(new Selection(8, 1, 8, 19)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(8), 'capital-snake-case'); + assertSelection(editor, new Selection(8, 1, 8, 19)); + + editor.setSelection(new Selection(9, 1, 9, 17)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(9), 'parse-html4-string'); + assertSelection(editor, new Selection(9, 1, 9, 19)); + + editor.setSelection(new Selection(10, 1, 10, 28)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(10), '_accessor: services-accessor'); + assertSelection(editor, new Selection(10, 1, 10, 29)); + + editor.setSelection(new Selection(11, 1, 11, 11)); + executeAction(kebabCaseAction, editor); + assert.strictEqual(model.getLineContent(11), 'kebab-case'); + assertSelection(editor, new Selection(11, 1, 11, 11)); + } + ); }); suite('DeleteAllRightAction', () => { diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index 44a5e2bd74f..adcb05a6a1f 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -163,14 +163,16 @@ export class LinkDetector extends Disposable implements IEditorContribution { } } - const decorations = this.editor.deltaDecorations(oldDecorations, newDecorations); + this.editor.changeDecorations((changeAccessor) => { + const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations); - this.currentOccurrences = {}; - this.activeLinkDecorationId = null; - for (let i = 0, len = decorations.length; i < len; i++) { - const occurence = new LinkOccurrence(links[i], decorations[i]); - this.currentOccurrences[occurence.decorationId] = occurence; - } + this.currentOccurrences = {}; + this.activeLinkDecorationId = null; + for (let i = 0, len = decorations.length; i < len; i++) { + const occurence = new LinkOccurrence(links[i], decorations[i]); + this.currentOccurrences[occurence.decorationId] = occurence; + } + }); } private _onEditorMouseMove(mouseEvent: ClickLinkMouseEvent, withKey: ClickLinkKeyboardEvent | null): void { diff --git a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts index 7220dbf1a4f..f72e2293aa7 100644 --- a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts @@ -10,7 +10,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Emitter } from 'vs/base/common/event'; +import { DebounceEmitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; @@ -38,7 +38,10 @@ export class MarkdownRenderer { } }); - private readonly _onDidRenderAsync = new Emitter(); + private readonly _onDidRenderAsync = new DebounceEmitter({ + delay: 50, + merge: arr => { } + }); readonly onDidRenderAsync = this._onDidRenderAsync.event; constructor( diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts index 1801ab78538..0702c58a85c 100644 --- a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts @@ -19,6 +19,8 @@ import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAcce import { localize } from 'vs/nls'; import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { Position } from 'vs/editor/common/core/position'; +import { findLast } from 'vs/base/common/arrays'; export interface IGotoSymbolQuickPickItem extends IQuickPickItem { kind: SymbolKind; @@ -155,7 +157,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Set initial picks and update on type let picksCts: CancellationTokenSource | undefined = undefined; - const updatePickerItems = async () => { + const updatePickerItems = async (positionToEnclose: Position | undefined) => { // Cancel any previous ask for picks and busy picksCts?.dispose(true); @@ -175,6 +177,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit if (items.length > 0) { picker.items = items; + if (positionToEnclose && query.original.length === 0) { + const candidate = findLast(items, item => Boolean(item.type !== 'separator' && item.range && Range.containsPosition(item.range.decoration, positionToEnclose))); + if (candidate) { + picker.activeItems = [candidate]; + } + } + } else { if (query.original.length > 0) { this.provideLabelPick(picker, localize('noMatchingSymbolResults', "No matching editor symbols")); @@ -188,19 +197,19 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit } } }; - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); + disposables.add(picker.onDidChangeValue(() => updatePickerItems(undefined))); + updatePickerItems(editor.getSelection()?.getPosition()); + // Reveal and decorate when active item changes - // However, ignore the very first event so that + // However, ignore the very first two events so that // opening the picker is not immediately revealing // and decorating the first entry. - let ignoreFirstActiveEvent = true; + let ignoreFirstActiveEvent = 2; disposables.add(picker.onDidChangeActive(() => { const [item] = picker.activeItems; if (item && item.range) { - if (ignoreFirstActiveEvent) { - ignoreFirstActiveEvent = false; + if (ignoreFirstActiveEvent-- > 0) { return; } diff --git a/src/vs/editor/contrib/snippet/browser/snippetController2.ts b/src/vs/editor/contrib/snippet/browser/snippetController2.ts index 43e72f6ce57..862f3a20493 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetController2.ts @@ -5,25 +5,26 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ISelection } from 'vs/editor/common/core/selection'; +import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CompletionItem, CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { Choice, SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { Choice } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { showSimpleSuggestions } from 'vs/editor/contrib/suggest/browser/suggest'; import { OvertypingCapturer } from 'vs/editor/contrib/suggest/browser/suggestOvertypingCapturer'; import { localize } from 'vs/nls'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; -import { SnippetSession } from './snippetSession'; +import { ISnippetEdit, SnippetSession } from './snippetSession'; export interface ISnippetInsertOptions { overwriteBefore: number; @@ -88,6 +89,19 @@ export class SnippetController2 implements IEditorContribution { this._snippetListener.dispose(); } + apply(edits: ISnippetEdit[], opts?: Partial) { + try { + this._doInsert(edits, typeof opts === 'undefined' ? _defaultOptions : { ..._defaultOptions, ...opts }); + + } catch (e) { + this.cancel(); + this._logService.error(e); + this._logService.error('snippet_error'); + this._logService.error('insert_edits=', edits); + this._logService.error('existing_template=', this._session ? this._session._logInfo() : ''); + } + } + insert( template: string, opts?: Partial @@ -108,7 +122,7 @@ export class SnippetController2 implements IEditorContribution { } private _doInsert( - template: string, + template: string | ISnippetEdit[], opts: ISnippetInsertOptions ): void { if (!this._editor.hasModel()) { @@ -123,11 +137,17 @@ export class SnippetController2 implements IEditorContribution { this._editor.getModel().pushStackElement(); } + // don't merge + if (this._session && typeof template !== 'string') { + this.cancel(); + } + if (!this._session) { this._modelVersionId = this._editor.getModel().getAlternativeVersionId(); this._session = new SnippetSession(this._editor, template, opts, this._languageConfigurationService); this._session.insert(); } else { + assertType(typeof template === 'string'); this._session.merge(template, opts); } @@ -342,50 +362,11 @@ export function performSnippetEdit(editor: ICodeEditor, snippet: string, selecti return false; } editor.focus(); - editor.setSelections(selections ?? []); - controller.insert(snippet); - return controller.isInSnippet(); -} - - -export type ISnippetEdit = { - range: Range; - snippet: string; -}; - -// --- - -export function performSnippetEdits(editor: ICodeEditor, edits: ISnippetEdit[]) { - - if (!editor.hasModel()) { - return false; - } - if (edits.length === 0) { - return false; - } - - const model = editor.getModel(); - let newText = ''; - let last: ISnippetEdit | undefined; - edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - - for (const item of edits) { - if (last) { - const between = Range.fromPositions(last.range.getEndPosition(), item.range.getStartPosition()); - const text = model.getValueInRange(between); - newText += SnippetParser.escape(text); - } - newText += item.snippet; - last = item; - } - - const controller = SnippetController2.get(editor); - if (!controller) { - return false; - } - model.pushStackElement(); - const range = Range.plusRange(edits[0].range, edits[edits.length - 1].range); - editor.setSelection(range); - controller.insert(newText, { undoStopBefore: false }); + controller.apply(selections.map(selection => { + return { + range: Selection.liftSelection(selection), + template: snippet + }; + })); return controller.isInSnippet(); } diff --git a/src/vs/editor/contrib/snippet/browser/snippetParser.ts b/src/vs/editor/contrib/snippet/browser/snippetParser.ts index 6faf65e0d4d..f68dcbfa953 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetParser.ts @@ -613,11 +613,17 @@ export class SnippetParser { } parse(value: string, insertFinalTabstop?: boolean, enforceFinalTabstop?: boolean): TextmateSnippet { + const snippet = new TextmateSnippet(); + this.parseFragment(value, snippet); + this.ensureFinalTabstop(snippet, enforceFinalTabstop ?? false, insertFinalTabstop ?? false); + return snippet; + } + parseFragment(value: string, snippet: TextmateSnippet): readonly Marker[] { + + const offset = snippet.children.length; this._scanner.text(value); this._token = this._scanner.next(); - - const snippet = new TextmateSnippet(); while (this._parse(snippet)) { // nothing } @@ -626,10 +632,8 @@ export class SnippetParser { // that has a value defines the value for all placeholders with that index const placeholderDefaultValues = new Map(); const incompletePlaceholders: Placeholder[] = []; - let placeholderCount = 0; snippet.walk(marker => { if (marker instanceof Placeholder) { - placeholderCount += 1; if (marker.isFinalTabstop) { placeholderDefaultValues.set(0, undefined); } else if (!placeholderDefaultValues.has(marker.index) && marker.children.length > 0) { @@ -640,6 +644,7 @@ export class SnippetParser { } return true; }); + for (const placeholder of incompletePlaceholders) { const defaultValues = placeholderDefaultValues.get(placeholder.index); if (defaultValues) { @@ -652,17 +657,20 @@ export class SnippetParser { } } - if (!enforceFinalTabstop) { - enforceFinalTabstop = placeholderCount > 0 && insertFinalTabstop; + return snippet.children.slice(offset); + } + + ensureFinalTabstop(snippet: TextmateSnippet, enforceFinalTabstop: boolean, insertFinalTabstop: boolean) { + + if (enforceFinalTabstop || insertFinalTabstop && snippet.placeholders.length > 0) { + const finalTabstop = snippet.placeholders.find(p => p.index === 0); + if (!finalTabstop) { + // the snippet uses placeholders but has no + // final tabstop defined -> insert at the end + snippet.appendChild(new Placeholder(0)); + } } - if (!placeholderDefaultValues.has(0) && enforceFinalTabstop) { - // the snippet uses placeholders but has no - // final tabstop defined -> insert at the end - snippet.appendChild(new Placeholder(0)); - } - - return snippet; } private _accept(type?: TokenType): boolean; diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts index 3ad72329e91..74eaa5847f7 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts @@ -55,7 +55,7 @@ export class OneSnippet { dispose(): void { if (this._placeholderDecorations) { - this._editor.deltaDecorations([...this._placeholderDecorations.values()], []); + this._editor.removeDecorations([...this._placeholderDecorations.values()]); } this._placeholderGroups.length = 0; } @@ -359,6 +359,11 @@ const _defaultOptions: ISnippetSessionInsertOptions = { overtypingCapturer: undefined }; +export interface ISnippetEdit { + range: Range; + template: string; +} + export class SnippetSession { static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): string { @@ -434,7 +439,7 @@ export class SnippetSession { return selection; } - static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } { + static createEditsAndSnippetsFromSelections(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } { const edits: IIdentifiedSingleEditOperation[] = []; const snippets: OneSnippet[] = []; @@ -518,22 +523,79 @@ export class SnippetSession { return { edits, snippets }; } - private readonly _editor: IActiveCodeEditor; - private readonly _template: string; - private readonly _templateMerges: [number, number, string][] = []; - private readonly _options: ISnippetSessionInsertOptions; + static createEditsAndSnippetsFromEdits(editor: IActiveCodeEditor, snippetEdits: ISnippetEdit[], enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } { + + if (!editor.hasModel() || snippetEdits.length === 0) { + return { edits: [], snippets: [] }; + } + + const edits: IIdentifiedSingleEditOperation[] = []; + const model = editor.getModel(); + + const parser = new SnippetParser(); + const snippet = new TextmateSnippet(); + + // + snippetEdits = snippetEdits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + let offset = 0; + for (let i = 0; i < snippetEdits.length; i++) { + + const { range, template } = snippetEdits[i]; + + // gaps between snippet edits are appended as text nodes. this + // ensures placeholder-offsets are later correct + if (i > 0) { + const lastRange = snippetEdits[i - 1].range; + const textRange = Range.fromPositions(lastRange.getEndPosition(), range.getStartPosition()); + const textNode = new Text(model.getValueInRange(textRange)); + snippet.appendChild(textNode); + offset += textNode.value.length; + } + + parser.parseFragment(template, snippet); + + const snippetText = snippet.toString(); + const snippetFragmentText = snippetText.slice(offset); + offset = snippetText.length; + + // make edit + const edit: IIdentifiedSingleEditOperation = EditOperation.replace(range, snippetFragmentText); + edit.identifier = { major: i, minor: 0 }; // mark the edit so only our undo edits will be used to generate end cursors + edit._isTracked = true; + edits.push(edit); + } + + // + parser.ensureFinalTabstop(snippet, enforceFinalTabstop, true); + + // snippet variables resolver + const resolver = new CompositeSnippetVariableResolver([ + editor.invokeWithinContext(accessor => new ModelBasedVariableResolver(accessor.get(ILabelService), model)), + new ClipboardBasedVariableResolver(() => clipboardText, 0, editor.getSelections().length, editor.getOption(EditorOption.multiCursorPaste) === 'spread'), + new SelectionBasedVariableResolver(model, editor.getSelection(), 0, overtypingCapturer), + new CommentBasedVariableResolver(model, editor.getSelection(), languageConfigurationService), + new TimeBasedVariableResolver, + new WorkspaceBasedVariableResolver(editor.invokeWithinContext(accessor => accessor.get(IWorkspaceContextService))), + new RandomBasedVariableResolver, + ]); + snippet.resolveVariables(resolver); + + + return { + edits, + snippets: [new OneSnippet(editor, snippet, '')] + }; + } + + private readonly _templateMerges: [number, number, string | ISnippetEdit[]][] = []; private _snippets: OneSnippet[] = []; constructor( - editor: IActiveCodeEditor, - template: string, - options: ISnippetSessionInsertOptions = _defaultOptions, + private readonly _editor: IActiveCodeEditor, + private readonly _template: string | ISnippetEdit[], + private readonly _options: ISnippetSessionInsertOptions = _defaultOptions, @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService - ) { - this._editor = editor; - this._template = template; - this._options = options; - } + ) { } dispose(): void { dispose(this._snippets); @@ -549,7 +611,10 @@ export class SnippetSession { } // make insert edit and start with first selections - const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService); + const { edits, snippets } = typeof this._template === 'string' + ? SnippetSession.createEditsAndSnippetsFromSelections(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService) + : SnippetSession.createEditsAndSnippetsFromEdits(this._editor, this._template, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService); + this._snippets = snippets; this._editor.executeEdits('snippet', edits, _undoEdits => { @@ -576,7 +641,7 @@ export class SnippetSession { return; } this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]); - const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText, options.overtypingCapturer, this._languageConfigurationService); + const { edits, snippets } = SnippetSession.createEditsAndSnippetsFromSelections(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText, options.overtypingCapturer, this._languageConfigurationService); this._editor.executeEdits('snippet', edits, _undoEdits => { // Sometimes, the text buffer will remove automatic whitespace when doing any edits, diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts index 3560a2c2be4..fce4d2c432b 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts @@ -7,6 +7,7 @@ import { mock } from 'vs/base/test/common/mock'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; +import { Range } from 'vs/editor/common/core/range'; import { Handler } from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; @@ -23,6 +24,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace suite('SnippetController2', function () { + /** @deprecated */ function assertSelections(editor: ICodeEditor, ...s: Selection[]) { for (const selection of editor.getSelections()!) { const actual = s.shift()!; @@ -31,10 +33,20 @@ suite('SnippetController2', function () { assert.strictEqual(s.length, 0); } + /** @deprecated */ function assertContextKeys(service: MockContextKeyService, inSnippet: boolean, hasPrev: boolean, hasNext: boolean): void { - assert.strictEqual(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`); - assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`); - assert.strictEqual(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`); + const state = getContextState(service); + assert.strictEqual(state.inSnippet, inSnippet, `inSnippetMode`); + assert.strictEqual(state.hasPrev, hasPrev, `HasPrevTabstop`); + assert.strictEqual(state.hasNext, hasNext, `HasNextTabstop`); + } + + function getContextState(service: MockContextKeyService = contextKeys) { + return { + inSnippet: SnippetController2.InSnippetMode.getValue(service), + hasPrev: SnippetController2.HasPrevTabstop.getValue(service), + hasNext: SnippetController2.HasNextTabstop.getValue(service), + }; } let editor: ICodeEditor; @@ -531,4 +543,153 @@ suite('SnippetController2', function () { assert.strictEqual(model.getValue(), `foo: number;\n\nfoo: 'number',`); // editor.trigger('test', 'type', { text: ';' }); }); + + suite('createEditsAndSnippetsFromEdits', function () { + + test('apply, tab, done', function () { + + const ctrl = instaService.createInstance(SnippetController2, editor); + + model.setValue('foo("bar")'); + + ctrl.apply([ + { range: new Range(1, 5, 1, 10), template: '$1' }, + { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar";\n' } + ]); + + assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)"); + assertContextKeys(contextKeys, true, false, true); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]); + + ctrl.next(); + assertContextKeys(contextKeys, false, false, false); + assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 14, 2, 14)]); + }); + + test('apply, tab, done with special final tabstop', function () { + + model.setValue('foo("bar")'); + + const ctrl = instaService.createInstance(SnippetController2, editor); + ctrl.apply([ + { range: new Range(1, 5, 1, 10), template: '$1' }, + { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const}$0 = "bar";\n' } + ]); + + assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)"); + assertContextKeys(contextKeys, true, false, true); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]); + + ctrl.next(); + assertContextKeys(contextKeys, false, false, false); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 16, 1, 16)]); + }); + + test('apply, tab, tab, done', function () { + + model.setValue('foo\nbar'); + + const ctrl = instaService.createInstance(SnippetController2, editor); + ctrl.apply([ + { range: new Range(1, 4, 1, 4), template: '${3}' }, + { range: new Range(2, 4, 2, 4), template: '$3' }, + { range: new Range(1, 1, 1, 1), template: '### ${2:Header}\n' } + ]); + + assert.strictEqual(model.getValue(), "### Header\nfoo\nbar"); + assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 11)]); + + ctrl.next(); + assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: true, hasNext: true }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 4, 2, 4), new Selection(3, 4, 3, 4)]); + + ctrl.next(); + assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(3, 4, 3, 4)]); + }); + + test('nested into apply works', function () { + + const ctrl = instaService.createInstance(SnippetController2, editor); + model.setValue('onetwo'); + + editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); + + ctrl.apply([{ + range: new Range(1, 7, 1, 7), + template: '$0${1:three}' + }]); + + assert.strictEqual(model.getValue(), 'onetwothree'); + assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 12)]); + + ctrl.insert('foo$1bar$1'); + assert.strictEqual(model.getValue(), 'onetwofoobar'); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 10, 1, 10), new Selection(1, 13, 1, 13)]); + assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: false, hasNext: true })); + + ctrl.next(); + assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: true, hasNext: true })); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 13, 1, 13)]); + + ctrl.next(); + assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 7)]); + + }); + + test('nested into insert abort "outer" snippet', function () { + + const ctrl = instaService.createInstance(SnippetController2, editor); + model.setValue('one\ntwo'); + + editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); + + ctrl.insert('foo${1:bar}bazz${1:bang}'); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]); + assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true }); + + ctrl.apply([{ + range: new Range(1, 4, 1, 7), + template: '$0A' + }]); + + assert.strictEqual(model.getValue(), 'fooAbazzbarone\nfoobarbazzbartwo'); + assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]); + }); + + test('nested into "insert" abort "outer" snippet (2)', function () { + + const ctrl = instaService.createInstance(SnippetController2, editor); + model.setValue('one\ntwo'); + + editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); + + ctrl.insert('foo${1:bar}bazz${1:bang}'); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]); + assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true }); + + const edits = [{ + range: new Range(1, 4, 1, 7), + template: 'A' + }, { + range: new Range(1, 11, 1, 14), + template: 'B' + }, { + range: new Range(2, 4, 2, 7), + template: 'C' + }, { + range: new Range(2, 11, 2, 14), + template: 'D' + }]; + ctrl.apply(edits); + + assert.strictEqual(model.getValue(), "fooAbazzBone\nfooCbazzDtwo"); + assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false }); + assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5), new Selection(1, 10, 1, 10), new Selection(2, 5, 2, 5), new Selection(2, 10, 2, 10)]); + }); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts index 7ce812e34ee..c9b7d0f2aa1 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts @@ -743,4 +743,36 @@ suite('SnippetSession', function () { '}', ].join('\n')); }); + + + suite('createEditsAndSnippetsFromEdits', function () { + + test('empty', function () { + + const result = SnippetSession.createEditsAndSnippetsFromEdits(editor, [], true, true, undefined, undefined, languageConfigurationService); + + assert.deepStrictEqual(result.edits, []); + assert.deepStrictEqual(result.snippets, []); + }); + + test('basic', function () { + + editor.getModel().setValue('foo("bar")'); + + const result = SnippetSession.createEditsAndSnippetsFromEdits( + editor, + [{ range: new Range(1, 5, 1, 9), template: '$1' }, { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar"' }], + true, true, undefined, undefined, languageConfigurationService + ); + + assert.strictEqual(result.edits.length, 2); + assert.deepStrictEqual(result.edits[0].range, new Range(1, 1, 1, 1)); + assert.deepStrictEqual(result.edits[0].text, 'const new_const = "bar"'); + assert.deepStrictEqual(result.edits[1].range, new Range(1, 5, 1, 9)); + assert.deepStrictEqual(result.edits[1].text, 'new_const'); + + assert.strictEqual(result.snippets.length, 1); + assert.strictEqual(result.snippets[0].isTrivialSnippet, false); + }); + }); }); diff --git a/src/vs/editor/contrib/suggest/browser/suggest.ts b/src/vs/editor/contrib/suggest/browser/suggest.ts index af54cf55c79..805c58df531 100644 --- a/src/vs/editor/contrib/suggest/browser/suggest.ts +++ b/src/vs/editor/contrib/suggest/browser/suggest.ts @@ -31,6 +31,7 @@ import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; export const Context = { Visible: historyNavigationVisible, + HasFocusedSuggestion: new RawContextKey('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")), DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")), MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")), MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true, localize('suggestionMakesTextEdit', "Whether inserting the current suggestion yields in a change or has everything already been typed")), @@ -323,9 +324,9 @@ function defaultComparator(a: CompletionItem, b: CompletionItem): number { } } // check with 'label' - if (a.completion.label < b.completion.label) { + if (a.textLabel < b.textLabel) { return -1; - } else if (a.completion.label > b.completion.label) { + } else if (a.textLabel > b.textLabel) { return 1; } // check with 'type' diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index c3b80a8c5e9..14045fe6bec 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -226,8 +226,11 @@ export class SuggestController implements IEditorContribution { this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position); })); this._toDispose.add(this.model.onDidSuggest(e => { - if (!e.shy) { - let index = -1; + if (e.shy) { + return; + } + let index = -1; + if (!e.noSelect) { for (const selector of this._selectors.itemsOrderedByPriorityDesc) { index = selector.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items); if (index !== -1) { @@ -237,8 +240,8 @@ export class SuggestController implements IEditorContribution { if (index === -1) { index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items); } - this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.auto); } + this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.auto); })); this._toDispose.add(this.model.onDidCancel(e => { if (!e.retrigger) { @@ -400,7 +403,7 @@ export class SuggestController implements IEditorContribution { } else if (item.completion.command.id === TriggerSuggestAction.id) { // retigger - this.model.trigger({ auto: true, shy: false }, true); + this.model.trigger({ auto: true, shy: false, noSelect: false }, true); } else { // exec command, done @@ -494,9 +497,9 @@ export class SuggestController implements IEditorContribution { } } - triggerSuggest(onlyFrom?: Set, auto?: boolean, noFilter?: boolean): void { + triggerSuggest(onlyFrom?: Set, auto?: boolean, noFilter?: boolean, noSelect?: boolean): void { if (this.editor.hasModel()) { - this.model.trigger({ auto: auto ?? false, shy: false }, false, onlyFrom, undefined, noFilter); + this.model.trigger({ auto: auto ?? false, shy: false, noSelect: noSelect ?? false }, false, onlyFrom, undefined, noFilter); this.editor.revealPosition(this.editor.getPosition(), ScrollType.Smooth); this.editor.focus(); } @@ -565,7 +568,7 @@ export class SuggestController implements IEditorContribution { }, undefined, listener); }); - this.model.trigger({ auto: false, shy: true }); + this.model.trigger({ auto: false, shy: true, noSelect: false }); this.editor.revealPosition(positionNow, ScrollType.Smooth); this.editor.focus(); } @@ -706,15 +709,19 @@ export class TriggerSuggestAction extends EditorAction { return; } - type TriggerArgs = { auto: boolean }; + type TriggerArgs = { auto: boolean; noSelection: boolean }; let auto: boolean | undefined; + let noSelect: boolean | undefined; if (args && typeof args === 'object') { if ((args).auto === true) { auto = true; } + if ((args).noSelection === true) { + noSelect = true; + } } - controller.triggerSuggest(undefined, auto); + controller.triggerSuggest(undefined, auto, undefined, noSelect); } } @@ -728,7 +735,7 @@ const SuggestCommand = EditorCommand.bindToContribution(Sugge registerEditorCommand(new SuggestCommand({ id: 'acceptSelectedSuggestion', - precondition: SuggestContext.Visible, + precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion), handler(x) { x.acceptSelectedSuggestion(true, false); }, @@ -766,7 +773,7 @@ registerEditorCommand(new SuggestCommand({ registerEditorCommand(new SuggestCommand({ id: 'acceptAlternativeSelectedSuggestion', - precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), + precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.HasFocusedSuggestion), kbOpts: { weight: weight, kbExpr: EditorContextKeys.textInputFocus, diff --git a/src/vs/editor/contrib/suggest/browser/suggestModel.ts b/src/vs/editor/contrib/suggest/browser/suggestModel.ts index 62f7bebafe1..7159e6a10ec 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestModel.ts @@ -44,11 +44,13 @@ export interface ISuggestEvent { readonly isFrozen: boolean; readonly auto: boolean; readonly shy: boolean; + readonly noSelect: boolean; } export interface SuggestTriggerContext { readonly auto: boolean; readonly shy: boolean; + readonly noSelect: boolean; readonly triggerKind?: CompletionTriggerKind; readonly triggerCharacter?: string; } @@ -82,14 +84,16 @@ export class LineContext { readonly leadingWord: IWordAtPosition; readonly auto: boolean; readonly shy: boolean; + readonly noSelect: boolean; - constructor(model: ITextModel, position: Position, auto: boolean, shy: boolean) { + constructor(model: ITextModel, position: Position, auto: boolean, shy: boolean, noSelect: boolean) { this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1); this.leadingWord = model.getWordUntilPosition(position); this.lineNumber = position.lineNumber; this.column = position.column; this.auto = auto; this.shy = shy; + this.noSelect = noSelect; } } @@ -279,7 +283,7 @@ export class SuggestModel implements IDisposable { const existing = this._completionModel ? { items: this._completionModel.adopt(supports), clipboardText: this._completionModel.clipboardText } : undefined; - this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, existing); + this.trigger({ auto: true, shy: false, noSelect: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, existing); } }; @@ -314,7 +318,7 @@ export class SuggestModel implements IDisposable { if (!this._editor.hasModel() || !this._languageFeaturesService.completionProvider.has(this._editor.getModel())) { this.cancel(); } else { - this.trigger({ auto: this._state === State.Auto, shy: false }, true); + this.trigger({ auto: this._state === State.Auto, shy: false, noSelect: false }, true); } } } @@ -413,7 +417,7 @@ export class SuggestModel implements IDisposable { } // we made it till here -> trigger now - this.trigger({ auto: true, shy: false }); + this.trigger({ auto: true, shy: false, noSelect: false }); }, this._editor.getOption(EditorOption.quickSuggestionsDelay)); } @@ -433,7 +437,7 @@ export class SuggestModel implements IDisposable { } const model = this._editor.getModel(); const position = this._editor.getPosition(); - const ctx = new LineContext(model, position, this._state === State.Auto, false); + const ctx = new LineContext(model, position, this._state === State.Auto, false, false); this._onNewContext(ctx); }); } @@ -445,7 +449,7 @@ export class SuggestModel implements IDisposable { const model = this._editor.getModel(); const auto = context.auto; - const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy); + const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy, context.noSelect); // Cancel previous requests, change state & update UI this.cancel(retrigger); @@ -520,7 +524,7 @@ export class SuggestModel implements IDisposable { items = items.concat(existing.items).sort(cmpFn); } - const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy); + const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy, context.noSelect); this._completionModel = new CompletionModel(items, this._context!.column, { leadingLineContent: ctx.leadingLineContent, characterCountDelta: ctx.column - this._context!.column @@ -630,7 +634,7 @@ export class SuggestModel implements IDisposable { if (ctx.column < this._context.column) { // typed -> moved cursor LEFT -> retrigger if still on a word if (ctx.leadingWord.word) { - this.trigger({ auto: this._context.auto, shy: false }, true); + this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true); } else { this.cancel(); } @@ -652,7 +656,7 @@ export class SuggestModel implements IDisposable { inactiveProvider.delete(provider); } const items = this._completionModel.adopt(new Set()); - this.trigger({ auto: this._context.auto, shy: false }, true, inactiveProvider, { items, clipboardText: this._completionModel.clipboardText }); + this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true, inactiveProvider, { items, clipboardText: this._completionModel.clipboardText }); return; } @@ -660,7 +664,7 @@ export class SuggestModel implements IDisposable { // typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger const { incomplete } = this._completionModel; const items = this._completionModel.adopt(incomplete); - this.trigger({ auto: this._state === State.Auto, shy: false, triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions }, true, incomplete, { items, clipboardText: this._completionModel.clipboardText }); + this.trigger({ auto: this._state === State.Auto, shy: false, noSelect: false, triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions }, true, incomplete, { items, clipboardText: this._completionModel.clipboardText }); } else { // typed -> moved cursor RIGHT -> update UI @@ -676,7 +680,7 @@ export class SuggestModel implements IDisposable { if (LineContext.shouldAutoTrigger(this._editor) && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) { // retrigger when heading into a new word - this.trigger({ auto: this._context.auto, shy: false }, true); + this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true); return; } @@ -703,6 +707,7 @@ export class SuggestModel implements IDisposable { completionModel: this._completionModel, auto: this._context.auto, shy: this._context.shy, + noSelect: this._context.noSelect, isFrozen, }); } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 3a03d2da7dd..cd32e70c678 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -30,7 +30,7 @@ import { attachListStyler } from 'vs/platform/theme/common/styler'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { CompletionModel } from './completionModel'; -import { ResizableHTMLElement } from './resizable'; +import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; import { CompletionItem, Context as SuggestContext } from './suggest'; import { canExpandCompletionItem, SuggestDetailsOverlay, SuggestDetailsWidget } from './suggestWidgetDetails'; import { getAriaId, ItemRenderer } from './suggestWidgetRenderer'; @@ -124,6 +124,7 @@ export class SuggestWidget implements IDisposable { private readonly _ctxSuggestWidgetVisible: IContextKey; private readonly _ctxSuggestWidgetDetailsVisible: IContextKey; private readonly _ctxSuggestWidgetMultipleSuggestions: IContextKey; + private readonly _ctxSuggestWidgetHasFocusedSuggestion: IContextKey; private readonly _showTimeout = new TimeoutTimer(); private readonly _disposables = new DisposableStore(); @@ -283,7 +284,7 @@ export class SuggestWidget implements IDisposable { this._ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(_contextKeyService); this._ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(_contextKeyService); this._ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(_contextKeyService); - + this._ctxSuggestWidgetHasFocusedSuggestion = SuggestContext.HasFocusedSuggestion.bindTo(_contextKeyService); this._disposables.add(dom.addStandardDisposableListener(this._details.widget.domNode, 'keydown', e => { this._onDetailsKeydown.fire(e); @@ -365,6 +366,7 @@ export class SuggestWidget implements IDisposable { } this.editor.setAriaOptions({ activeDescendant: undefined }); + this._ctxSuggestWidgetHasFocusedSuggestion.set(false); return; } @@ -372,6 +374,7 @@ export class SuggestWidget implements IDisposable { return; } + this._ctxSuggestWidgetHasFocusedSuggestion.set(true); const item = e.elements[0]; const index = e.indexes[0]; @@ -440,6 +443,7 @@ export class SuggestWidget implements IDisposable { this._contentWidget.hide(); this._ctxSuggestWidgetVisible.reset(); this._ctxSuggestWidgetMultipleSuggestions.reset(); + this._ctxSuggestWidgetHasFocusedSuggestion.reset(); this._showTimeout.cancel(); this.element.domNode.classList.remove('visible'); this._list.splice(0, this._list.length); @@ -538,8 +542,10 @@ export class SuggestWidget implements IDisposable { this._focusedItem = undefined; this._list.splice(0, this._list.length, this._completionModel.items); this._setState(isFrozen ? State.Frozen : State.Open); - this._list.reveal(selectionIndex, 0); - this._list.setFocus([selectionIndex]); + if (selectionIndex >= 0) { + this._list.reveal(selectionIndex, 0); + this._list.setFocus([selectionIndex]); + } this._layout(this.element.size); // Reset focus border diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index e9d6c4c5130..cd6e10771c0 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -12,7 +12,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { ICodeEditor, IOverlayWidget } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ResizableHTMLElement } from 'vs/editor/contrib/suggest/browser/resizable'; +import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable'; import * as nls from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CompletionItem } from './suggest'; diff --git a/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts index 7a474b09e9e..42b961a69ad 100644 --- a/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/completionModel.test.ts @@ -10,13 +10,13 @@ import { CompletionModel } from 'vs/editor/contrib/suggest/browser/completionMod import { CompletionItem, getSuggestionComparator, SnippetSortOrder } from 'vs/editor/contrib/suggest/browser/suggest'; import { WordDistance } from 'vs/editor/contrib/suggest/browser/wordDistance'; -export function createSuggestItem(label: string, overwriteBefore: number, kind = languages.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem { +export function createSuggestItem(label: string | languages.CompletionItemLabel, overwriteBefore: number, kind = languages.CompletionItemKind.Property, incomplete: boolean = false, position: IPosition = { lineNumber: 1, column: 1 }, sortText?: string, filterText?: string): CompletionItem { const suggestion: languages.CompletionItem = { label, sortText, filterText, range: { startLineNumber: position.lineNumber, startColumn: position.column - overwriteBefore, endLineNumber: position.lineNumber, endColumn: position.column }, - insertText: label, + insertText: typeof label === 'string' ? label : label.label, kind }; const container: languages.CompletionList = { @@ -275,6 +275,17 @@ suite('CompletionModel', function () { assert.strictEqual(second.completion.label, '<- groups'); }); + test('Completion item sorting broken when using label details #153026', function () { + const itemZZZ = createSuggestItem({ label: 'ZZZ' }, 0, languages.CompletionItemKind.Operator, false); + const itemAAA = createSuggestItem({ label: 'AAA' }, 0, languages.CompletionItemKind.Operator, false); + const itemIII = createSuggestItem('III', 0, languages.CompletionItemKind.Operator, false); + + const cmp = getSuggestionComparator(SnippetSortOrder.Inline); + const actual = [itemZZZ, itemAAA, itemIII].sort(cmp); + + assert.deepStrictEqual(actual, [itemAAA, itemIII, itemZZZ]); + }); + test('Score only filtered items when typing more, score all when typing less', function () { model = new CompletionModel([ createSuggestItem('console', 0), diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts index f25e0b22040..0a1b3dda2de 100644 --- a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts @@ -253,7 +253,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return Promise.all([ assertEvent(model.onDidTrigger, function () { - model.trigger({ auto: true, shy: false }); + model.trigger({ auto: true, shy: false, noSelect: false }); }, function (event) { assert.strictEqual(event.auto, true); @@ -265,13 +265,13 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }), assertEvent(model.onDidTrigger, function () { - model.trigger({ auto: true, shy: false }); + model.trigger({ auto: true, shy: false, noSelect: false }); }, function (event) { assert.strictEqual(event.auto, true); }), assertEvent(model.onDidTrigger, function () { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, function (event) { assert.strictEqual(event.auto, false); }) @@ -287,12 +287,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return withOracle(model => { return Promise.all([ assertEvent(model.onDidCancel, function () { - model.trigger({ auto: true, shy: false }); + model.trigger({ auto: true, shy: false, noSelect: false }); }, function (event) { assert.strictEqual(event.retrigger, false); }), assertEvent(model.onDidSuggest, function () { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, function (event) { assert.strictEqual(event.auto, false); assert.strictEqual(event.isFrozen, false); @@ -343,7 +343,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return assertEvent(model.onDidSuggest, () => { // make sure completionModel starts here! - model.trigger({ auto: true, shy: false }); + model.trigger({ auto: true, shy: false, noSelect: false }); }, event => { return assertEvent(model.onDidSuggest, () => { @@ -443,7 +443,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 3 }); return assertEvent(model.onDidSuggest, () => { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, event => { assert.strictEqual(event.auto, false); assert.strictEqual(event.isFrozen, false); @@ -468,7 +468,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 3 }); return assertEvent(model.onDidSuggest, () => { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, event => { assert.strictEqual(event.auto, false); assert.strictEqual(event.isFrozen, false); @@ -505,7 +505,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 4 }); return assertEvent(model.onDidSuggest, () => { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, event => { assert.strictEqual(event.auto, false); assert.strictEqual(event.completionModel.incomplete.size, 1); @@ -542,7 +542,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { editor.setPosition({ lineNumber: 1, column: 4 }); return assertEvent(model.onDidSuggest, () => { - model.trigger({ auto: false, shy: false }); + model.trigger({ auto: false, shy: false, noSelect: false }); }, event => { assert.strictEqual(event.auto, false); assert.strictEqual(event.completionModel.incomplete.size, 1); @@ -701,7 +701,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { await assertEvent(sugget.onDidSuggest, () => { editor.setPosition({ lineNumber: 1, column: 3 }); - sugget.trigger({ auto: false, shy: false }); + sugget.trigger({ auto: false, shy: false, noSelect: false }); }, event => { assert.strictEqual(event.completionModel.items.length, 1); @@ -928,4 +928,19 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }); }); }); + + test('noSelect-flag makes it from request to suggest event', function () { + + disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport)); + + return withOracle((model, editor) => { + return assertEvent(model.onDidSuggest, () => { + model.trigger({ auto: false, noSelect: true, shy: false }); + }, event => { + assert.strictEqual(event.noSelect, true); + assert.strictEqual(event.auto, false); + assert.strictEqual(event.shy, false); + }); + }); + }); }); diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 5e96676ba40..8356bd94844 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -40,7 +40,7 @@ import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/com import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter, IFormatterChangeEvent } from 'vs/platform/label/common/label'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { IProgressRunner, IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, IWorkspaceFoldersWillChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -232,8 +232,12 @@ export class StandaloneNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; + readonly onDidChangeDoNotDisturbMode: Event = Event.None; + public _serviceBrand: undefined; + public doNotDisturbMode: boolean = false; + private static readonly NO_OP: INotificationHandle = new NoOpNotification(); public info(message: string): INotificationHandle { @@ -271,8 +275,6 @@ export class StandaloneNotificationService implements INotificationService { public status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - - public setFilter(filter: NotificationsFilter): void { } } export class StandaloneCommandService implements ICommandService { diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index 4690b299892..1366682b48f 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -17,13 +17,13 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { asCssVariableName, ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IProductIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { ColorScheme, isDark } from 'vs/platform/theme/common/theme'; +import { ColorScheme, isDark, isHighContrast } from 'vs/platform/theme/common/theme'; import { getIconsStyleSheet, UnthemedProductIconTheme } from 'vs/platform/theme/browser/iconsStyleSheet'; -const VS_THEME_NAME = 'vs'; -const VS_DARK_THEME_NAME = 'vs-dark'; -const HC_BLACK_THEME_NAME = 'hc-black'; -const HC_LIGHT_THEME_NAME = 'hc-light'; +export const VS_LIGHT_THEME_NAME = 'vs'; +export const VS_DARK_THEME_NAME = 'vs-dark'; +export const HC_BLACK_THEME_NAME = 'hc-black'; +export const HC_LIGHT_THEME_NAME = 'hc-light'; const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); @@ -118,7 +118,7 @@ class StandaloneTheme implements IStandaloneTheme { public get type(): ColorScheme { switch (this.base) { - case VS_THEME_NAME: return ColorScheme.LIGHT; + case VS_LIGHT_THEME_NAME: return ColorScheme.LIGHT; case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST_DARK; case HC_LIGHT_THEME_NAME: return ColorScheme.HIGH_CONTRAST_LIGHT; default: return ColorScheme.DARK; @@ -182,7 +182,7 @@ class StandaloneTheme implements IStandaloneTheme { function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { return ( - themeName === VS_THEME_NAME + themeName === VS_LIGHT_THEME_NAME || themeName === VS_DARK_THEME_NAME || themeName === HC_BLACK_THEME_NAME || themeName === HC_LIGHT_THEME_NAME @@ -191,7 +191,7 @@ function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { function getBuiltinRules(builtinTheme: BuiltinTheme): IStandaloneThemeData { switch (builtinTheme) { - case VS_THEME_NAME: + case VS_LIGHT_THEME_NAME: return vs; case VS_DARK_THEME_NAME: return vs_dark; @@ -229,7 +229,6 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe private _globalStyleElement: HTMLStyleElement | null; private _styleElements: HTMLStyleElement[]; private _colorMapOverride: Color[] | null; - private _desiredTheme!: IStandaloneTheme; private _theme!: IStandaloneTheme; private _builtInProductIconTheme = new UnthemedProductIconTheme(); @@ -240,7 +239,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this._autoDetectHighContrast = true; this._knownThemes = new Map(); - this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME)); + this._knownThemes.set(VS_LIGHT_THEME_NAME, newBuiltInTheme(VS_LIGHT_THEME_NAME)); this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME)); this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME)); this._knownThemes.set(HC_LIGHT_THEME_NAME, newBuiltInTheme(HC_LIGHT_THEME_NAME)); @@ -253,7 +252,8 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this._globalStyleElement = null; this._styleElements = []; this._colorMapOverride = null; - this.setTheme(VS_THEME_NAME); + this.setTheme(VS_LIGHT_THEME_NAME); + this._onOSSchemeChanged(); iconsStyleSheet.onDidChange(() => { this._codiconCSS = iconsStyleSheet.getCSS(); @@ -261,7 +261,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe }); addMatchMediaChangeListener('(forced-colors: active)', () => { - this._updateActualTheme(); + this._onOSSchemeChanged(); }); } @@ -331,41 +331,43 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe } public setTheme(themeName: string): void { - let theme: StandaloneTheme; + let theme: StandaloneTheme | undefined; if (this._knownThemes.has(themeName)) { - theme = this._knownThemes.get(themeName)!; + theme = this._knownThemes.get(themeName); } else { - theme = this._knownThemes.get(VS_THEME_NAME)!; + theme = this._knownThemes.get(VS_LIGHT_THEME_NAME); } - this._desiredTheme = theme; - this._updateActualTheme(); + this._updateActualTheme(theme); } - private getHighContrastTheme() { - if (isDark(this._desiredTheme.type)) { - return HC_BLACK_THEME_NAME; - } else { - return HC_LIGHT_THEME_NAME; - } - } - - private _updateActualTheme(): void { - const theme = ( - this._autoDetectHighContrast && window.matchMedia(`(forced-colors: active)`).matches - ? this._knownThemes.get(this.getHighContrastTheme())! - : this._desiredTheme - ); - if (this._theme === theme) { + private _updateActualTheme(desiredTheme: IStandaloneTheme | undefined): void { + if (!desiredTheme || this._theme === desiredTheme) { // Nothing to do return; } - this._theme = theme; + this._theme = desiredTheme; this._updateThemeOrColorMap(); } + private _onOSSchemeChanged() { + if (this._autoDetectHighContrast) { + const wantsHighContrast = window.matchMedia(`(forced-colors: active)`).matches; + if (wantsHighContrast !== isHighContrast(this._theme.type)) { + // switch to high contrast or non-high contrast but stick to dark or light + let newThemeName; + if (isDark(this._theme.type)) { + newThemeName = wantsHighContrast ? HC_BLACK_THEME_NAME : VS_DARK_THEME_NAME; + } else { + newThemeName = wantsHighContrast ? HC_LIGHT_THEME_NAME : VS_LIGHT_THEME_NAME; + } + this._updateActualTheme(this._knownThemes.get(newThemeName)); + } + } + } + public setAutoDetectHighContrast(autoDetectHighContrast: boolean): void { this._autoDetectHighContrast = autoDetectHighContrast; - this._updateActualTheme(); + this._onOSSchemeChanged(); } private _updateThemeOrColorMap(): void { diff --git a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts index d10e1ccafb9..4fe1552ebd5 100644 --- a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts +++ b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts @@ -7,7 +7,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme'; import { ToggleHighContrastNLS } from 'vs/editor/common/standaloneStrings'; -import { isHighContrast } from 'vs/platform/theme/common/theme'; +import { isDark, isHighContrast } from 'vs/platform/theme/common/theme'; +import { HC_BLACK_THEME_NAME, HC_LIGHT_THEME_NAME, VS_DARK_THEME_NAME, VS_LIGHT_THEME_NAME } from 'vs/editor/standalone/browser/standaloneThemeService'; class ToggleHighContrast extends EditorAction { @@ -25,13 +26,14 @@ class ToggleHighContrast extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const standaloneThemeService = accessor.get(IStandaloneThemeService); - if (isHighContrast(standaloneThemeService.getColorTheme().type)) { + const currentTheme = standaloneThemeService.getColorTheme(); + if (isHighContrast(currentTheme.type)) { // We must toggle back to the integrator's theme - standaloneThemeService.setTheme(this._originalThemeName || 'vs'); + standaloneThemeService.setTheme(this._originalThemeName || (isDark(currentTheme.type) ? VS_DARK_THEME_NAME : VS_LIGHT_THEME_NAME)); this._originalThemeName = null; } else { - this._originalThemeName = standaloneThemeService.getColorTheme().themeName; - standaloneThemeService.setTheme('hc-black'); + standaloneThemeService.setTheme(isDark(currentTheme.type) ? HC_BLACK_THEME_NAME : HC_LIGHT_THEME_NAME); + this._originalThemeName = currentTheme.themeName; } } } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 41eba1043ba..3615f378627 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -502,18 +502,20 @@ suite('Editor Controller - Cursor', () => { ); withTestCodeEditor(model, { wrappingIndent: 'indent', wordWrap: 'wordWrapColumn', wordWrapColumn: 20 }, (editor, viewModel) => { - editor.deltaDecorations([], [ - { - range: new Range(1, 22, 1, 22), - options: { - showIfCollapsed: true, - description: 'test', - after: { - content: 'some very very very very very very very very long text', + editor.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations([], [ + { + range: new Range(1, 22, 1, 22), + options: { + showIfCollapsed: true, + description: 'test', + after: { + content: 'some very very very very very very very very long text', + } } } - } - ]); + ]); + }); viewModel.setSelections('test', [new Selection(1, 1, 1, 1)]); const cursorPositions: any[] = []; diff --git a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts index 878ac72db23..7fa0217572a 100644 --- a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts +++ b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts @@ -192,7 +192,9 @@ suite('CodeEditorWidget', () => { const calls: string[] = []; disposables.add(editor.onDidChangeModelContent((e) => { calls.push(`listener1 - contentchange(${e.changes.reduce((aggr, c) => [...aggr, c.text, c.rangeOffset, c.rangeLength], []).join(', ')})`); - editor.deltaDecorations([], [{ range: new Range(1, 1, 1, 1), options: { description: 'test' } }]); + editor.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations([], [{ range: new Range(1, 1, 1, 1), options: { description: 'test' } }]); + }); })); disposables.add(editor.onDidChangeCursorSelection((e) => { calls.push(`listener1 - cursorchange(${e.selection.positionLineNumber}, ${e.selection.positionColumn})`); diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 2d0a32d5d0e..25c141d009b 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -15,9 +15,7 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguageConfigurationService, LanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; suite('TokensStore', () => { @@ -222,9 +220,9 @@ suite('TokensStore', () => { test('issue #147944: Language id "vs.editor.nullLanguage" is not configured nor known', () => { const disposables = new DisposableStore(); - const instantiationService = createModelServices(disposables, new ServiceCollection([ - ILanguageConfigurationService, new SyncDescriptor(LanguageConfigurationService) - ])); + const instantiationService = createModelServices(disposables, [ + [ILanguageConfigurationService, LanguageConfigurationService] + ]); const model = instantiateTextModel(instantiationService, '--[[\n\n]]'); model.tokenization.setSemanticTokens([ SparseMultilineTokens.create(1, new Uint32Array([ diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 8fc6d1e76c0..4774a9ec930 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -258,4 +258,11 @@ suite('Editor Modes - Link Computer', () => { 'https://site.web/page.html ' ); }); + + test('issue #151631: Link parsing stoped where comments include a single quote ', () => { + assertLink( + `aa https://regexper.com/#%2F''%2F aa`, + ` https://regexper.com/#%2F''%2F `, + ); + }); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 7cf81039b95..90c5cfbadc8 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -37,7 +37,6 @@ import { LanguageFeatureDebounceService } from 'vs/editor/common/services/langua import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -55,11 +54,9 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - const serviceCollection = new ServiceCollection([ - IConfigurationService, configService + instantiationService = createModelServices(disposables, [ + [IConfigurationService, configService] ]); - - instantiationService = createModelServices(disposables, serviceCollection); modelService = instantiationService.get(IModelService); }); diff --git a/src/vs/editor/test/common/testTextModel.ts b/src/vs/editor/test/common/testTextModel.ts index 1d12991f5a2..b445b6452ed 100644 --- a/src/vs/editor/test/common/testTextModel.ts +++ b/src/vs/editor/test/common/testTextModel.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { BracketPairColorizationOptions, DefaultEndOfLine, ITextBufferFactory, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -16,9 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; @@ -29,7 +27,7 @@ import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; import { IModelService } from 'vs/editor/common/services/model'; import { ModelService } from 'vs/editor/common/services/modelService'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { createServices, ServiceIdCtorPair, TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; @@ -87,36 +85,19 @@ export function instantiateTextModel(instantiationService: IInstantiationService return instantiationService.createInstance(TestTextModel, text, languageId || PLAINTEXT_LANGUAGE_ID, options, uri); } -export function createModelServices(disposables: DisposableStore, services: ServiceCollection = new ServiceCollection()): TestInstantiationService { - const serviceIdentifiers: ServiceIdentifier[] = []; - const define = (id: ServiceIdentifier, ctor: new (...args: any[]) => T) => { - if (!services.has(id)) { - services.set(id, new SyncDescriptor(ctor)); - } - serviceIdentifiers.push(id); - }; - - define(INotificationService, TestNotificationService); - define(IDialogService, TestDialogService); - define(IUndoRedoService, UndoRedoService); - define(ILanguageService, LanguageService); - define(ILanguageConfigurationService, TestLanguageConfigurationService); - define(IConfigurationService, TestConfigurationService); - define(ITextResourcePropertiesService, TestTextResourcePropertiesService); - define(IThemeService, TestThemeService); - define(ILogService, NullLogService); - define(ILanguageFeatureDebounceService, LanguageFeatureDebounceService); - define(ILanguageFeaturesService, LanguageFeaturesService); - define(IModelService, ModelService); - - const instantiationService = new TestInstantiationService(services, true); - disposables.add(toDisposable(() => { - for (const id of serviceIdentifiers) { - const instanceOrDescriptor = services.get(id); - if (typeof instanceOrDescriptor.dispose === 'function') { - instanceOrDescriptor.dispose(); - } - } - })); - return instantiationService; +export function createModelServices(disposables: DisposableStore, services: ServiceIdCtorPair[] = []): TestInstantiationService { + return createServices(disposables, services.concat([ + [INotificationService, TestNotificationService], + [IDialogService, TestDialogService], + [IUndoRedoService, UndoRedoService], + [ILanguageService, LanguageService], + [ILanguageConfigurationService, TestLanguageConfigurationService], + [IConfigurationService, TestConfigurationService], + [ITextResourcePropertiesService, TestTextResourcePropertiesService], + [IThemeService, TestThemeService], + [ILogService, NullLogService], + [ILanguageFeatureDebounceService, LanguageFeatureDebounceService], + [ILanguageFeaturesService, LanguageFeaturesService], + [IModelService, ModelService], + ])); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 593c213681f..f608381f9e4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3325,7 +3325,7 @@ declare namespace monaco.editor { * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. * Defaults to 'mouseover'. */ - showFoldingControls?: 'always' | 'mouseover'; + showFoldingControls?: 'always' | 'never' | 'mouseover'; /** * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. @@ -3458,6 +3458,11 @@ declare namespace monaco.editor { * Defaults to true. */ renderIndicators?: boolean; + /** + * Shows icons in the glyph margin to revert changes. + * Default to true. + */ + renderMarginRevertIcon?: boolean; /** * Original model should be editable? * Defaults to false. @@ -4534,7 +4539,7 @@ declare namespace monaco.editor { selectionClipboard: IEditorOption; selectionHighlight: IEditorOption; selectOnLineNumbers: IEditorOption; - showFoldingControls: IEditorOption; + showFoldingControls: IEditorOption; showUnused: IEditorOption; showDeprecated: IEditorOption; inlayHints: IEditorOption>>; @@ -5308,9 +5313,13 @@ declare namespace monaco.editor { getDecorationsInRange(range: Range): IModelDecoration[] | null; /** * All decorations added through this call will get the ownerId of this editor. - * @see {@link ITextModel.deltaDecorations} + * @deprecated */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; + /** + * Remove previously added decorations. + */ + removeDecorations(decorationIds: string[]): void; /** * Get the layout info for the editor. */ @@ -7006,22 +7015,24 @@ declare namespace monaco.languages { maxSize?: number; } - export interface WorkspaceFileEdit { - oldUri?: Uri; - newUri?: Uri; + export interface IWorkspaceFileEdit { + oldResource?: Uri; + newResource?: Uri; options?: WorkspaceFileEditOptions; metadata?: WorkspaceEditMetadata; } - export interface WorkspaceTextEdit { + export interface IWorkspaceTextEdit { resource: Uri; - edit: TextEdit; - modelVersionId?: number; + textEdit: TextEdit & { + insertAsSnippet?: boolean; + }; + versionId: number | undefined; metadata?: WorkspaceEditMetadata; } export interface WorkspaceEdit { - edits: Array; + edits: Array; } export interface Rejection { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 1f9da3b627a..d1cd1197cce 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -59,6 +59,7 @@ export class MenuId { static readonly SimpleEditorContext = new MenuId('SimpleEditorContext'); static readonly EditorContextCopy = new MenuId('EditorContextCopy'); static readonly EditorContextPeek = new MenuId('EditorContextPeek'); + static readonly EditorContextShare = new MenuId('EditorContextShare'); static readonly EditorTitle = new MenuId('EditorTitle'); static readonly EditorTitleRun = new MenuId('EditorTitleRun'); static readonly EditorTitleContext = new MenuId('EditorTitleContext'); @@ -85,6 +86,7 @@ export class MenuId { static readonly MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu'); static readonly MenubarRecentMenu = new MenuId('MenubarRecentMenu'); static readonly MenubarSelectionMenu = new MenuId('MenubarSelectionMenu'); + static readonly MenubarShare = new MenuId('MenubarShare'); static readonly MenubarSwitchEditorMenu = new MenuId('MenubarSwitchEditorMenu'); static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu'); static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu'); diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 4b8f39a1840..e0f60280e33 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -150,11 +150,20 @@ class Menu implements IMenu { const activeActions: Array = []; for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { - const action = isIMenuItem(item) - ? new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService) - : new SubmenuItemAction(item, this._menuService, this._contextKeyService, options); + let action: MenuItemAction | SubmenuItemAction | undefined; + if (isIMenuItem(item)) { + action = new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService); + } else { + action = new SubmenuItemAction(item, this._menuService, this._contextKeyService, options); + if (action.actions.length === 0) { + action.dispose(); + action = undefined; + } + } - activeActions.push(action); + if (action) { + activeActions.push(action); + } } } if (activeActions.length > 0) { diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 02a9d6fc848..07a136ef7b9 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -225,6 +225,9 @@ class SimpleContextKeyChangeEvent implements IContextKeyChangeEvent { affectsSome(keys: IReadableSet): boolean { return keys.has(this.key); } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.affectsSome(keys); + } } class ArrayContextKeyChangeEvent implements IContextKeyChangeEvent { @@ -237,6 +240,9 @@ class ArrayContextKeyChangeEvent implements IContextKeyChangeEvent { } return false; } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.keys.every(key => keys.has(key)); + } } class CompositeContextKeyChangeEvent implements IContextKeyChangeEvent { @@ -249,12 +255,13 @@ class CompositeContextKeyChangeEvent implements IContextKeyChangeEvent { } return false; } + allKeysContainedIn(keys: IReadableSet): boolean { + return this.events.every(evt => evt.allKeysContainedIn(keys)); + } } function allEventKeysInContext(event: IContextKeyChangeEvent, context: Record): boolean { - return (event instanceof ArrayContextKeyChangeEvent && event.keys.every(key => key in context)) || - (event instanceof SimpleContextKeyChangeEvent && event.key in context) || - (event instanceof CompositeContextKeyChangeEvent && event.events.every(e => allEventKeysInContext(e, context))); + return event.allKeysContainedIn(new Set(Object.keys(context))); } export abstract class AbstractContextKeyService implements IContextKeyService { diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 1ce10689a3a..c55d029b8e8 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -1597,6 +1597,7 @@ export interface IReadableSet { export interface IContextKeyChangeEvent { affectsSome(keys: IReadableSet): boolean; + allKeysContainedIn(keys: IReadableSet): boolean; } export interface IContextKeyService { diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts index 03c40dd332c..de968735c0a 100644 --- a/src/vs/platform/contextkey/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -16,6 +16,7 @@ export const IsMacNativeContext = new RawContextKey('isMacNative', isMa export const IsIOSContext = new RawContextKey('isIOS', isIOS, localize('isIOS', "Whether the operating system is iOS")); export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true); +export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code")); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box")); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index aa9c2407af0..0e8ccc14714 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -86,7 +86,6 @@ export interface NativeParsedArgs { 'force-user-env'?: boolean; 'force-disable-user-env'?: boolean; 'sync'?: 'on' | 'off'; - '__sandbox'?: boolean; 'logsPath'?: string; '__enable-file-policy'?: boolean; editSessionId?: string; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 5ed69a092ef..32fcb903d4c 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -66,6 +66,7 @@ export interface IEnvironmentService { // --- continue edit session editSessionId?: string; + editSessionsLogResource: URI; // --- extension development debugExtensionHost: IExtensionHostDebugParams; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 992c7ed9a96..90b24b9c924 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -80,6 +80,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron @memoize get userDataSyncLogResource(): URI { return URI.file(join(this.logsPath, 'userDataSync.log')); } + @memoize + get editSessionsLogResource(): URI { return URI.file(join(this.logsPath, 'editSessions.log')); } + @memoize get sync(): 'on' | 'off' | undefined { return this.args.sync; } diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 6834b8115fe..53aabe4147c 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -34,7 +34,6 @@ export interface IEnvironmentMainService extends INativeEnvironmentService { mainLockfile: string; // --- config - sandbox: boolean; disableUpdates: boolean; } @@ -55,9 +54,6 @@ export class EnvironmentMainService extends NativeEnvironmentService implements @memoize get mainLockfile(): string { return join(this.userDataPath, 'code.lock'); } - @memoize - get sandbox(): boolean { return !!this.args['__sandbox']; } - @memoize get disableUpdates(): boolean { return !!this.args['disable-updates']; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index bb409289757..e27a89390c2 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -124,7 +124,6 @@ export const OPTIONS: OptionDescriptions> = { 'force-user-env': { type: 'boolean' }, 'force-disable-user-env': { type: 'boolean' }, 'open-devtools': { type: 'boolean' }, - '__sandbox': { type: 'boolean' }, 'logsPath': { type: 'string' }, '__enable-file-policy': { type: 'boolean' }, 'editSessionId': { type: 'string' }, diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 99d049079ed..66e250a2df1 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -14,7 +14,8 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { ExtensionManagementError, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementParticipant, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, - IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, IServerExtensionManagementService, ServerInstallOptions, ServerInstallVSIXOptions, ServerUninstallOptions, Metadata, ServerInstallExtensionEvent, ServerInstallExtensionResult, ServerUninstallExtensionEvent, ServerDidUninstallExtensionEvent + IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, IServerExtensionManagementService, + ServerInstallOptions, ServerInstallVSIXOptions, ServerUninstallOptions, Metadata, ServerInstallExtensionEvent, ServerInstallExtensionResult, ServerUninstallExtensionEvent, ServerDidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; @@ -34,8 +35,6 @@ export interface IInstallExtensionTask { cancel(): void; } -export type UninstallExtensionTaskOptions = { readonly remove?: boolean; readonly versionOnly?: boolean }; - export interface IUninstallExtensionTask { readonly extension: ILocalExtension; run(): Promise; @@ -225,10 +224,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, options, CancellationToken.None))); if (!URI.isUri(task.source)) { const isUpdate = task.operation === InstallOperation.Update; + const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(task.source), duration: new Date().getTime() - startTime, - durationSinceUpdate: isUpdate ? undefined : new Date().getTime() - task.source.lastUpdated + durationSinceUpdate }); // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. if (isWeb && task.operation !== InstallOperation.Update) { @@ -426,53 +426,64 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } private async unininstallExtension(extension: ILocalExtension, options: ServerUninstallOptions): Promise { - if (!options.profileLocation) { - const uninstallExtensionTask = this.uninstallingExtensions.get(extension.identifier.id.toLowerCase()); - if (uninstallExtensionTask) { - this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); - return uninstallExtensionTask.waitUntilTaskIsFinished(); - } + const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${options.profileLocation ? `@${options.profileLocation.toString()}` : ''}`; + const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier)); + if (uninstallExtensionTask) { + this.logService.info('Extensions is already requested to uninstall', extension.identifier.id); + return uninstallExtensionTask.waitUntilTaskIsFinished(); } - const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: UninstallExtensionTaskOptions): IUninstallExtensionTask => { + const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: ServerUninstallOptions): IUninstallExtensionTask => { const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions, options.profileLocation); - this.uninstallingExtensions.set(uninstallExtensionTask.extension.identifier.id.toLowerCase(), uninstallExtensionTask); - this.logService.info('Uninstalling extension:', extension.identifier.id); + this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask); + if (options.profileLocation) { + this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString()); + } else { + this.logService.info('Uninstalling extension:', `${extension.identifier.id}@${extension.manifest.version}`); + } this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped }); return uninstallExtensionTask; }; const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => { if (error) { - this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message); + if (options.profileLocation) { + this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString(), error.message); + } else { + this.logService.error('Failed to uninstall extension:', `${extension.identifier.id}@${extension.manifest.version}`, error.message); + } } else { - this.logService.info('Successfully uninstalled extension:', extension.identifier.id); + if (options.profileLocation) { + this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString()); + } else { + this.logService.info('Successfully uninstalled extension:', `${extension.identifier.id}@${extension.manifest.version}`); + } } reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error }); - this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped }); + this._onDidUninstallExtension.fire({ identifier: extension.identifier, version: extension.manifest.version, error: error?.code, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped }); }; const allTasks: IUninstallExtensionTask[] = []; const processedTasks: IUninstallExtensionTask[] = []; try { - allTasks.push(createUninstallExtensionTask(extension, {})); + allTasks.push(createUninstallExtensionTask(extension, options)); const installed = await this.getInstalled(ExtensionType.User, options.profileLocation); if (options.donotIncludePack) { - this.logService.info('Uninstalling the extension without including packed extension', extension.identifier.id); + this.logService.info('Uninstalling the extension without including packed extension', `${extension.identifier.id}@${extension.manifest.version}`); } else { const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); for (const packedExtension of packedExtensions) { - if (this.uninstallingExtensions.has(packedExtension.identifier.id.toLowerCase())) { + if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension.identifier))) { this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id); } else { - allTasks.push(createUninstallExtensionTask(packedExtension, {})); + allTasks.push(createUninstallExtensionTask(packedExtension, options)); } } } if (options.donotCheckDependents) { - this.logService.info('Uninstalling the extension without checking dependents', extension.identifier.id); + this.logService.info('Uninstalling the extension without checking dependents', `${extension.identifier.id}@${extension.manifest.version}`); } else { this.checkForDependents(allTasks.map(task => task.extension), installed, extension); } @@ -511,7 +522,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } finally { // Remove tasks from cache for (const task of allTasks) { - if (!this.uninstallingExtensions.delete(task.extension.identifier.id.toLowerCase())) { + if (!this.uninstallingExtensions.delete(getUninstallExtensionTaskKey(task.extension.identifier))) { this.logService.warn('Uninstallation task is not found in the cache', task.extension.identifier.id); } } @@ -607,7 +618,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return new InstallExtensionInProfileTask(installTask, options.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, this.extensionsProfileScannerService); } - private createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions, profile?: URI): IUninstallExtensionTask { + private createUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions, profile?: URI): IUninstallExtensionTask { return profile && this.userDataProfilesService.defaultProfile.extensionsResource ? new UninstallExtensionFromProfileTask(extension, profile, this.userDataProfilesService, this.extensionsProfileScannerService) : this.createDefaultUninstallExtensionTask(extension, options); } @@ -623,7 +634,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise; protected abstract createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask; - protected abstract createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask; + protected abstract createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask; } export function joinErrors(errorOrErrors: (Error | string) | (Array)): Error { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6f04a76edb6..3515dea4464 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -584,7 +584,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; this.extensionsControlUrl = config && config.controlUrl; - this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, productService, this.environmentService, this.configurationService, this.fileService, storageService); + this.commonHeadersPromise = resolveMarketplaceHeaders( + productService.version, + productService, + this.environmentService, + this.configurationService, + this.fileService, + storageService, + this.telemetryService); } private api(path = ''): string { @@ -928,7 +935,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi 'Content-Type': 'application/json', 'Accept': 'application/json;api-version=3.0-preview.1', 'Accept-Encoding': 'gzip', - 'Content-Length': String(data.length) + 'Content-Length': String(data.length), }; const startTime = new Date().getTime(); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 4e376a1a4c8..9302da87da0 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -361,8 +361,9 @@ export interface UninstallExtensionEvent { } export interface DidUninstallExtensionEvent { - identifier: IExtensionIdentifier; - error?: string; + readonly identifier: IExtensionIdentifier; + readonly version?: string; + readonly error?: string; } export enum ExtensionManagementErrorCode { @@ -403,7 +404,7 @@ export type InstallOptions = { context?: IStringDictionary; }; export type InstallVSIXOptions = Omit & { installOnlyNewlyAddedFromExtensionPack?: boolean }; -export type UninstallOptions = { donotIncludePack?: boolean; donotCheckDependents?: boolean }; +export type UninstallOptions = { readonly donotIncludePack?: boolean; readonly donotCheckDependents?: boolean; readonly versionOnly?: boolean; readonly remove?: boolean }; export interface IExtensionManagementParticipant { postInstall(local: ILocalExtension, source: URI | IGalleryExtension, options: InstallOptions | InstallVSIXOptions, token: CancellationToken): Promise; @@ -527,3 +528,10 @@ export interface IExtensionManagementCLIService { uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise; locateExtension(extensions: string[], output?: CLIOutput): Promise; } + +export const IDefaultExtensionsProfileInitService = createDecorator('IDefaultExtensionsProfileInitService'); +export interface IDefaultExtensionsProfileInitService { + readonly _serviceBrand: undefined; + initialize(): Promise; + uninitialize(): Promise; +} diff --git a/src/vs/platform/extensionManagement/common/extensionTipsService.ts b/src/vs/platform/extensionManagement/common/extensionTipsService.ts index 4e59c5a3b47..c9c4aaf97dd 100644 --- a/src/vs/platform/extensionManagement/common/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/common/extensionTipsService.ts @@ -5,7 +5,6 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { forEach } from 'vs/base/common/collections'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product'; import { joinPath } from 'vs/base/common/resources'; @@ -31,7 +30,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ) { super(); if (this.productService.configBasedExtensionTips) { - forEach(this.productService.configBasedExtensionTips, ({ value }) => this.allConfigBasedTips.set(value.configPath, value)); + Object.entries(this.productService.configBasedExtensionTips).forEach(([, value]) => this.allConfigBasedTips.set(value.configPath, value)); } } @@ -60,7 +59,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe try { const content = await this.fileService.readFile(joinPath(folder, configPath)); const recommendationByRemote: Map = new Map(); - forEach(tip.recommendations, ({ key, value }) => { + Object.entries(tip.recommendations).forEach(([key, value]) => { if (isNonEmptyArray(value.remotes)) { for (const remote of value.remotes) { recommendationByRemote.set(remote, { diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 0dfadaa8c49..43c8cfd1e19 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -16,13 +16,15 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ILogService } from 'vs/platform/log/common/log'; interface IStoredProfileExtension { - readonly identifier: IExtensionIdentifier; - readonly location: UriComponents; - readonly metadata?: Metadata; + identifier: IExtensionIdentifier; + location: UriComponents; + version: string; + metadata?: Metadata; } export interface IScannedProfileExtension { readonly identifier: IExtensionIdentifier; + readonly version: string; readonly location: URI; readonly metadata?: Metadata; } @@ -56,7 +58,7 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte return this.withProfileExtensions(profileLocation, profileExtensions => { // Remove the existing extension to avoid duplicates profileExtensions = profileExtensions.filter(e => extensions.some(([extension]) => !areSameExtensions(e.identifier, extension.identifier))); - profileExtensions.push(...extensions.map(([extension, metadata]) => ({ identifier: extension.identifier, location: extension.location, metadata }))); + profileExtensions.push(...extensions.map(([extension, metadata]) => ({ identifier: extension.identifier, version: extension.manifest.version, location: extension.location, metadata }))); return profileExtensions; }); } @@ -74,13 +76,22 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte const content = await this.fileService.readFile(file); const storedWebExtensions: IStoredProfileExtension[] = JSON.parse(content.value.toString()); for (const e of storedWebExtensions) { - if (!e.location || !e.identifier) { - this.logService.info('Ignoring invalid extension while scanning', storedWebExtensions); + if (!e.identifier) { + this.logService.info('Ignoring invalid extension while scanning. Identifier does not exist.', e); + continue; + } + if (!e.location) { + this.logService.info('Ignoring invalid extension while scanning. Location does not exist.', e); + continue; + } + if (!e.version) { + this.logService.info('Ignoring invalid extension while scanning. Version does not exist.', e); continue; } extensions.push({ identifier: e.identifier, location: URI.revive(e.location), + version: e.version, metadata: e.metadata, }); } @@ -96,6 +107,7 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte extensions = updateFn(extensions); const storedProfileExtensions: IStoredProfileExtension[] = extensions.map(e => ({ identifier: e.identifier, + version: e.version, location: e.location.toJSON(), metadata: e.metadata })); diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 29a0264d9cc..823a0be2541 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -381,7 +381,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean = true): Promise { const translations = await this.getTranslations(language ?? platform.language); const mtime = await this.getMtime(location); - const applicationExtensionsLocation = this.userDataProfilesService.defaultProfile.extensionsResource; + const applicationExtensionsLocation = profile ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; const applicationExtensionsLocationMtime = applicationExtensionsLocation ? await this.getMtime(applicationExtensionsLocation) : undefined; return new ExtensionScannerInput( location, @@ -512,7 +512,9 @@ class ExtensionsScanner extends Disposable { const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations); return this.scanExtension(extensionScannerInput); })); - return coalesce(extensions); + return coalesce(extensions) + // Sort: Make sure extensions are in the same order always. Helps cache invalidation even if the order changes. + .sort((a, b) => a.location.path < b.location.path ? -1 : 1); } private async scanExtensionsFromProfile(input: ExtensionScannerInput): Promise { diff --git a/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts b/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts new file mode 100644 index 00000000000..fd3d1698683 --- /dev/null +++ b/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; + +export class DefaultExtensionsProfileInitHandler extends Disposable { + constructor( + @IDefaultExtensionsProfileInitService private readonly defaultExtensionsProfileInitService: IDefaultExtensionsProfileInitService, + @IUserDataProfilesMainService userDataProfilesService: IUserDataProfilesMainService, + ) { + super(); + this._register(userDataProfilesService.onWillCreateProfile(e => { + if (userDataProfilesService.profiles.length === 1) { + e.join(this.defaultExtensionsProfileInitService.initialize()); + } + })); + this._register(userDataProfilesService.onDidChangeProfiles(e => { + if (userDataProfilesService.profiles.length === 1) { + this.defaultExtensionsProfileInitService.uninitialize(); + } + })); + } +} diff --git a/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts b/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts new file mode 100644 index 00000000000..d353dc77727 --- /dev/null +++ b/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IDefaultExtensionsProfileInitService, IExtensionManagementService, ILocalExtension, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { EXTENSIONS_RESOURCE_NAME, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + +export class DefaultExtensionsProfileInitService extends Disposable implements IDefaultExtensionsProfileInitService { + + readonly _serviceBrand: undefined; + + constructor( + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IFileService private readonly fileService: IFileService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + ) { + super(); + } + + async initialize(): Promise { + /* Create and populate the default extensions profile resource */ + const extensionsProfileResource = this.getDefaultExtensionsProfileResource(); + try { await this.fileService.del(extensionsProfileResource); } catch (error) { /* ignore */ } + const userExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(userExtensions.map(async e => ([e, await this.extensionManagementService.getMetadata(e)]))); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, extensionsProfileResource); + } + + async uninitialize(): Promise { + /* Remove the default extensions profile resource */ + try { await this.fileService.del(this.getDefaultExtensionsProfileResource()); } catch (error) { /* ignore */ } + } + + private getDefaultExtensionsProfileResource(): URI { + return this.userDataProfilesService.defaultProfile.extensionsResource ?? joinPath(this.userDataProfilesService.defaultProfile.location, EXTENSIONS_RESOURCE_NAME); + } +} diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index c6e03e0bf5f..fdfb6721b9a 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -5,7 +5,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { disposableTimeout, timeout } from 'vs/base/common/async'; -import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { join } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; @@ -67,11 +67,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService { ) { super(fileService, productService, requestService, logService); if (productService.exeBasedExtensionTips) { - forEach(productService.exeBasedExtensionTips, ({ key, value: exeBasedExtensionTip }) => { + Object.entries(productService.exeBasedExtensionTips).forEach(([key, exeBasedExtensionTip]) => { const highImportanceRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = []; const mediumImportanceRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = []; const otherRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = []; - forEach(exeBasedExtensionTip.recommendations, ({ key: extensionId, value }) => { + Object.entries(exeBasedExtensionTip.recommendations).forEach(([extensionId, value]) => { if (value.important) { if (exeBasedExtensionTip.important) { highImportanceRecommendations.push({ extensionId, extensionName: value.name, isExtensionPack: !!value.isExtensionPack }); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4c2d6a47d6b..56ea181d99e 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -22,10 +22,11 @@ import { extract, ExtractError, IFile, zip } from 'vs/base/node/zip'; import * as nls from 'vs/nls'; import { IDownloadService } from 'vs/platform/download/common/download'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, - Metadata, ServerInstallOptions, ServerInstallVSIXOptions + IServerExtensionManagementService, + Metadata, ServerInstallOptions, ServerInstallVSIXOptions, ServerUninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; @@ -38,7 +39,7 @@ import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensio import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -51,7 +52,14 @@ interface InstallableExtension { metadata?: Metadata; } -export class ExtensionManagementService extends AbstractExtensionManagementService { +export const INativeServerExtensionManagementService = refineServiceDecorator(IServerExtensionManagementService); +export interface INativeServerExtensionManagementService extends IServerExtensionManagementService { + readonly _serviceBrand: undefined; + removeUninstalledExtensions(removeOutdated: boolean): Promise; + getAllUserInstalled(): Promise; +} + +export class ExtensionManagementService extends AbstractExtensionManagementService implements INativeServerExtensionManagementService { private readonly extensionsScanner: ExtensionsScanner; private readonly manifestCache: ExtensionsManifestCache; @@ -117,6 +125,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.extensionsScanner.scanExtensions(type ?? null, profileLocation); } + getAllUserInstalled(): Promise { + return this.extensionsScanner.scanUserExtensions(false); + } + async install(vsix: URI, options: ServerInstallVSIXOptions = {}): Promise { this.logService.trace('ExtensionManagementService#install', vsix.toString()); @@ -151,8 +163,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return local; } - removeDeprecatedExtensions(): Promise { - return this.extensionsScanner.cleanUp(); + removeUninstalledExtensions(removeOutdated: boolean): Promise { + return this.extensionsScanner.cleanUp(removeOutdated); } private async downloadVsix(vsix: URI): Promise { @@ -168,7 +180,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return URI.isUri(extension) ? new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService) : new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService); } - protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { + protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask { return new UninstallExtensionTask(extension, options, this.extensionsScanner); } @@ -215,9 +227,11 @@ class ExtensionsScanner extends Disposable { this.uninstalledFileLimiter = new Queue(); } - async cleanUp(): Promise { + async cleanUp(removeOutdated: boolean): Promise { await this.removeUninstalledExtensions(); - await this.removeOutdatedExtensions(); + if (removeOutdated) { + await this.removeOutdatedExtensions(); + } } async scanExtensions(type: ExtensionType | null, profileLocation: URI | undefined): Promise { @@ -581,7 +595,7 @@ class InstallGalleryExtensionTask extends InstallExtensionTask { const zipPath = await this.downloadExtension(this.gallery, this._operation); try { const local = await this.installExtension({ zipPath, key: ExtensionKey.create(this.gallery), metadata }, token); - if (existingExtension && (existingExtension.targetPlatform !== local.targetPlatform || semver.neq(existingExtension.manifest.version, local.manifest.version))) { + if (existingExtension && !this.options.profileLocation && (existingExtension.targetPlatform !== local.targetPlatform || semver.neq(existingExtension.manifest.version, local.manifest.version))) { await this.extensionsScanner.setUninstalled(existingExtension); } return { local, metadata }; @@ -650,7 +664,7 @@ class InstallVSIXTask extends InstallExtensionTask { } catch (e) { throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } - } else if (semver.gt(existing.manifest.version, this.manifest.version)) { + } else if (!this.options.profileLocation && semver.gt(existing.manifest.version, this.manifest.version)) { await this.extensionsScanner.setUninstalled(existing); } } else { @@ -696,7 +710,7 @@ class UninstallExtensionTask extends AbstractExtensionTask implements IUni constructor( readonly extension: ILocalExtension, - private readonly options: UninstallExtensionTaskOptions, + private readonly options: ServerUninstallOptions, private readonly extensionsScanner: ExtensionsScanner, ) { super(); diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 4312de805f8..7dcb41d56bf 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -23,6 +23,7 @@ import { resolveMarketplaceHeaders } from 'vs/platform/externalServices/common/m import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; import { TelemetryConfiguration, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; class EnvironmentServiceMock extends mock() { override readonly serviceMachineIdResource: URI; @@ -52,9 +53,9 @@ suite('Extension Gallery Service', () => { teardown(() => disposables.clear()); test('marketplace machine id', async () => { - const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService); + const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.ok(isUUID(headers['X-Market-User-Id'])); - const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService); + const headers2 = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.strictEqual(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts index ca52229c320..027748ea8c1 100644 --- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -324,6 +324,7 @@ class UtilityExtensionHostProcess extends Disposable { const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath; const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock']; const execArgv: string[] = opts.execArgv || []; + execArgv.push(`--vscode-utility-kind=extensionHost`); const env: { [key: string]: any } = { ...opts.env }; // Make sure all values are strings, otherwise the process will not start diff --git a/src/vs/platform/externalServices/common/marketplace.ts b/src/vs/platform/externalServices/common/marketplace.ts index 6d2c25ab86d..5923e1cd8e8 100644 --- a/src/vs/platform/externalServices/common/marketplace.ts +++ b/src/vs/platform/externalServices/common/marketplace.ts @@ -10,17 +10,26 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -export async function resolveMarketplaceHeaders(version: string, productService: IProductService, environmentService: IEnvironmentService, configurationService: IConfigurationService, fileService: IFileService, storageService: IStorageService | undefined): Promise { +export async function resolveMarketplaceHeaders(version: string, + productService: IProductService, + environmentService: IEnvironmentService, + configurationService: IConfigurationService, + fileService: IFileService, + storageService: IStorageService | undefined, + telemetryService: ITelemetryService): Promise { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version} (${productService.nameShort})` }; const uuid = await getServiceMachineId(environmentService, fileService, storageService); + const { sessionId } = await telemetryService.getTelemetryInfo(); + if (supportsTelemetry(productService, environmentService) && getTelemetryLevel(configurationService) === TelemetryLevel.USAGE) { headers['X-Market-User-Id'] = uuid; + headers['VSCode-SessionId'] = sessionId; } return headers; } diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts index 36b2f65cdfc..af4458fe1c0 100644 --- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts +++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts @@ -253,7 +253,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst private readonly _onReportError = this._register(new Emitter()); readonly onReportError = this._onReportError.event; - private readonly versions = new Map(); + private readonly mtimes = new Map(); private cachedFiletree: Promise | undefined; private writeManyThrottler: Throttler; @@ -289,7 +289,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst return { type: FileType.File, ctime: 0, - mtime: this.versions.get(resource.toString()) || 0, + mtime: this.mtimes.get(resource.toString()) || 0, size: entry.size ?? (await this.readFile(resource)).byteLength }; } @@ -434,7 +434,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst } await this.deleteKeys(toDelete); (await this.getFiletree()).delete(resource.path); - toDelete.forEach(key => this.versions.delete(key)); + toDelete.forEach(key => this.mtimes.delete(key)); this.triggerChanges(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED }))); } @@ -487,7 +487,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst const fileTree = await this.getFiletree(); for (const [resource, content] of files) { fileTree.add(resource.path, { type: 'file', size: content.byteLength }); - this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1); + this.mtimes.set(resource.toString(), Date.now()); } this.triggerChanges(files.map(([resource]) => ({ resource, type: FileChangeType.UPDATED }))); diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 56509eb740f..66b0bc116eb 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as parcelWatcher from '@parcel/watcher'; -import { existsSync, unlinkSync } from 'fs'; +import { existsSync, statSync, unlinkSync } from 'fs'; import { tmpdir } from 'os'; import { DeferredPromise, RunOnceScheduler, ThrottledWorker } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -653,7 +653,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } } - protected normalizeRequests(requests: IRecursiveWatchRequest[]): IRecursiveWatchRequest[] { + protected normalizeRequests(requests: IRecursiveWatchRequest[], validatePaths = true): IRecursiveWatchRequest[] { const requestTrie = TernarySearchTree.forPaths(!isLinux); // Sort requests by path length to have shortest first @@ -674,16 +674,35 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { continue; // path is ignored entirely (via `**` glob exclude) } + // Check for overlapping requests if (requestTrie.findSubstr(request.path)) { try { const realpath = realpathSync(request.path); if (realpath === request.path) { this.trace(`ignoring a path for watching who's parent is already watched: ${request.path}`); - continue; // path is not a symbolic link or similar + continue; } } catch (error) { - continue; // invalid path - ignore from watching + this.trace(`ignoring a path for watching who's realpath failed to resolve: ${request.path} (error: ${error})`); + + continue; + } + } + + // Check for invalid paths + if (validatePaths) { + try { + const stat = statSync(request.path); + if (!stat.isDirectory()) { + this.trace(`ignoring a path for watching that is a file and not a folder: ${request.path}`); + + continue; + } + } catch (error) { + this.trace(`ignoring a path for watching who's stat info failed to resolve: ${request.path} (error: ${error})`); + + continue; } } diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 0a38f1b2f85..de1f4521122 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -1103,7 +1103,7 @@ flakySuite('Disk File Service', function () { } }); - test('copy - MIX CASE different taget - overwrite', async () => { + test('copy - MIX CASE different target - overwrite', async () => { const source1 = await service.resolve(URI.file(join(testDir, 'index.html')), { resolveMetadata: true }); assert.ok(source1.size > 0); diff --git a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index 052e2ffb603..3196a4b6271 100644 --- a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -33,7 +33,7 @@ import { ltrim } from 'vs/base/common/strings'; return { path, excludes, recursive: true }; }); - return this.normalizeRequests(requests).map(request => request.path); + return this.normalizeRequests(requests, false /* validate paths skipped for tests */).map(request => request.path); } override async watch(requests: IRecursiveWatchRequest[]): Promise { @@ -155,7 +155,7 @@ import { ltrim } from 'vs/base/common/strings'; } test('basics', async function () { - await watcher.watch([{ path: testDir, excludes: [], recursive: true }]); // + await watcher.watch([{ path: testDir, excludes: [], recursive: true }]); // New file const newFilePath = join(testDir, 'deep', 'newFile.txt'); @@ -430,6 +430,16 @@ import { ltrim } from 'vs/base/common/strings'; await changeFuture; }); + test('invalid path does not crash watcher', async function () { + await watcher.watch([ + { path: testDir, excludes: [], recursive: true }, + { path: join(testDir, 'invalid-folder'), excludes: [], recursive: true }, + { path: __filename, excludes: [], recursive: true } + ]); + + return basicCrudTest(join(testDir, 'deep', 'newFile.txt')); + }); + test('subsequent watch updates watchers (excludes)', async function () { await watcher.watch([{ path: testDir, excludes: [realpathSync(testDir)], recursive: true }]); await watcher.watch([{ path: testDir, excludes: [], recursive: true }]); diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index b4253b7765c..1f6bba77e24 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -258,7 +258,7 @@ export class InstantiationService implements IInstantiationService { private _throwIfStrict(msg: string, printWarning: boolean): void { if (printWarning) { - console.warn(printWarning); + console.warn(msg); } if (this._strict) { throw new Error(msg); diff --git a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts index cf8f82fe1fd..993fbfe9643 100644 --- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts +++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as sinon from 'sinon'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService, Trace } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -129,3 +131,36 @@ interface SinonOptions { mock?: boolean; stub?: boolean; } + +export type ServiceIdCtorPair = [id: ServiceIdentifier, ctorOrInstance: T | (new (...args: any[]) => T)]; + +export function createServices(disposables: DisposableStore, services: ServiceIdCtorPair[]): TestInstantiationService { + const serviceIdentifiers: ServiceIdentifier[] = []; + const serviceCollection = new ServiceCollection(); + + const define = (id: ServiceIdentifier, ctorOrInstance: T | (new (...args: any[]) => T)) => { + if (!serviceCollection.has(id)) { + if (typeof ctorOrInstance === 'function') { + serviceCollection.set(id, new SyncDescriptor(ctorOrInstance as any)); + } else { + serviceCollection.set(id, ctorOrInstance); + } + } + serviceIdentifiers.push(id); + }; + + for (const [id, ctor] of services) { + define(id, ctor); + } + + const instantiationService = new TestInstantiationService(serviceCollection, true); + disposables.add(toDisposable(() => { + for (const id of serviceIdentifiers) { + const instanceOrDescriptor = serviceCollection.get(id); + if (typeof instanceOrDescriptor.dispose === 'function') { + instanceOrDescriptor.dispose(); + } + } + })); + return instantiationService; +} diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 550c83be0d9..cbc4d251934 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -59,6 +59,7 @@ export interface IssueReporterData extends WindowData { extensionId?: string; experiments?: string; restrictedMode: boolean; + isUnsupported: boolean; githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index a0d963aa542..cada490a6a0 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -141,8 +141,10 @@ suite('AbstractKeybindingService', () => { const notificationService: INotificationService = { _serviceBrand: undefined, + doNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, + onDidChangeDoNotDisturbMode: undefined!, notify: (notification: INotification) => { showMessageCalls.push({ sev: notification.severity, message: notification.message }); return new NoOpNotification(); @@ -169,8 +171,7 @@ suite('AbstractKeybindingService', () => { statusMessageCallsDisposed!.push(message); } }; - }, - setFilter() { } + } }; const resolver = new KeybindingResolver(items, [], () => { }); diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index e662f5a0016..3c82e108ea0 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -319,6 +319,13 @@ export interface INotificationService { readonly _serviceBrand: undefined; + /** + * The DND mode can be enabled or disabled + * and will result in all info and warning + * notifications to be silent. + */ + doNotDisturbMode: boolean; + /** * Emitted when a new notification is added. */ @@ -329,6 +336,11 @@ export interface INotificationService { */ readonly onDidRemoveNotification: Event; + /** + * Emitted when a do not disturb mode has changed. + */ + readonly onDidChangeDoNotDisturbMode: Event; + /** * Show the provided notification to the user. The returned `INotificationHandle` * can be used to control the notification afterwards. @@ -381,13 +393,6 @@ export interface INotificationService { * @returns a disposable to hide the status message */ status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; - - /** - * Allows to configure a filter for notifications. - * - * @param filter the filter to use - */ - setFilter(filter: NotificationsFilter): void; } export class NoOpNotification implements INotificationHandle { diff --git a/src/vs/platform/notification/test/common/testNotificationService.ts b/src/vs/platform/notification/test/common/testNotificationService.ts index fe227b1f9fd..9d248ee5cd5 100644 --- a/src/vs/platform/notification/test/common/testNotificationService.ts +++ b/src/vs/platform/notification/test/common/testNotificationService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NotificationsFilter, Severity } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, Severity } from 'vs/platform/notification/common/notification'; export class TestNotificationService implements INotificationService { @@ -13,8 +13,12 @@ export class TestNotificationService implements INotificationService { readonly onDidRemoveNotification: Event = Event.None; + readonly onDidChangeDoNotDisturbMode: Event = Event.None; + declare readonly _serviceBrand: undefined; + doNotDisturbMode: boolean = false; + private static readonly NO_OP: INotificationHandle = new NoOpNotification(); info(message: string): INotificationHandle { @@ -40,6 +44,4 @@ export class TestNotificationService implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - - setFilter(filter: NotificationsFilter): void { } } diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 570f41553a6..de09d638262 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -555,7 +555,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor return false; // both profiles are same } - if (isProfileUsingDefaultStorage(to) === isProfileUsingDefaultStorage(from)) { + if (isProfileUsingDefaultStorage(to) && isProfileUsingDefaultStorage(from)) { return false; // both profiles are using default } diff --git a/src/vs/platform/storage/electron-sandbox/storageService.ts b/src/vs/platform/storage/electron-sandbox/storageService.ts index 9a27bd23135..d5bf2548f20 100644 --- a/src/vs/platform/storage/electron-sandbox/storageService.ts +++ b/src/vs/platform/storage/electron-sandbox/storageService.ts @@ -16,30 +16,24 @@ import { IAnyWorkspaceIdentifier, IEmptyWorkspaceIdentifier, ISingleFolderWorksp export class NativeStorageService extends AbstractStorageService { - private readonly applicationStorage: IStorage; - private readonly applicationStorageProfile: IUserDataProfile; + private readonly applicationStorageProfile = this.initialProfiles.defaultProfile; + private readonly applicationStorage = this.createApplicationStorage(); - private profileStorage: IStorage; - private profileStorageProfile: IUserDataProfile | undefined = undefined; + private profileStorageProfile = this.initialProfiles.currentProfile; private readonly profileStorageDisposables = this._register(new DisposableStore()); + private profileStorage = this.createProfileStorage(this.profileStorageProfile); - private workspaceStorage: IStorage | undefined = undefined; - private workspaceStorageId: string | undefined = undefined; + private workspaceStorageId = this.initialWorkspace?.id; private readonly workspaceStorageDisposables = this._register(new DisposableStore()); + private workspaceStorage = this.createWorkspaceStorage(this.initialWorkspace); constructor( - workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined, - { defaultProfile, currentProfile }: { defaultProfile: IUserDataProfile; currentProfile: IUserDataProfile }, + private readonly initialWorkspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined, + private readonly initialProfiles: { defaultProfile: IUserDataProfile; currentProfile: IUserDataProfile }, private readonly mainProcessService: IMainProcessService, private readonly environmentService: IEnvironmentService ) { super(); - - this.applicationStorageProfile = defaultProfile; - - this.applicationStorage = this.createApplicationStorage(); - this.profileStorage = this.createProfileStorage(currentProfile); - this.workspaceStorage = this.createWorkspaceStorage(workspace); } private createApplicationStorage(): IStorage { @@ -148,7 +142,7 @@ export class NativeStorageService extends AbstractStorageService { } protected async switchToProfile(toProfile: IUserDataProfile, preserveData: boolean): Promise { - if (this.profileStorageProfile && !this.canSwitchProfile(this.profileStorageProfile, toProfile)) { + if (!this.canSwitchProfile(this.profileStorageProfile, toProfile)) { return; } diff --git a/src/vs/platform/telemetry/browser/1dsAppender.ts b/src/vs/platform/telemetry/browser/1dsAppender.ts index 93bf07cdbb6..96db1e7890b 100644 --- a/src/vs/platform/telemetry/browser/1dsAppender.ts +++ b/src/vs/platform/telemetry/browser/1dsAppender.ts @@ -4,16 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import type { AppInsightsCore } from '@microsoft/1ds-core-js'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender'; export class OneDataSystemWebAppender extends AbstractOneDataSystemAppender { constructor( + configurationService: IConfigurationService, eventPrefix: string, defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing ) { - super(eventPrefix, defaultData, iKeyOrClientFactory); + super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory); // If we cannot fetch the endpoint it means it is down and we should not send any telemetry. // This is most likely due to ad blockers diff --git a/src/vs/platform/telemetry/browser/appInsightsAppender.ts b/src/vs/platform/telemetry/browser/appInsightsAppender.ts deleted file mode 100644 index cd4b32c13af..00000000000 --- a/src/vs/platform/telemetry/browser/appInsightsAppender.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { ApplicationInsights } from '@microsoft/applicationinsights-web'; -import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; - -export class WebAppInsightsAppender implements ITelemetryAppender { - private _aiClient: ApplicationInsights | undefined; - private _aiClientLoaded = false; - private _telemetryCache: { eventName: string; data: any }[] = []; - - constructor(private _eventPrefix: string, aiKey: string) { - const endpointUrl = 'https://vscode.vortex.data.microsoft.com/collect/v1'; - import('@microsoft/applicationinsights-web').then(aiLibrary => { - this._aiClient = new aiLibrary.ApplicationInsights({ - config: { - instrumentationKey: aiKey, - endpointUrl, - disableAjaxTracking: true, - disableExceptionTracking: true, - disableFetchTracking: true, - disableCorrelationHeaders: true, - disableCookiesUsage: true, - autoTrackPageVisitTime: false, - emitLineDelimitedJson: true, - }, - }); - this._aiClient.loadAppInsights(); - // Client is loaded we can now flush the cached events - this._aiClientLoaded = true; - this._telemetryCache.forEach(cacheEntry => this.log(cacheEntry.eventName, cacheEntry.data)); - this._telemetryCache = []; - - // If we cannot access the endpoint this most likely means it's being blocked - // and we should not attempt to send any telemetry. - fetch(endpointUrl, { method: 'POST' }).catch(() => (this._aiClient = undefined)); - }).catch(err => { - console.error(err); - }); - } - - /** - * Logs a telemetry event with eventName and data - * @param eventName The event name - * @param data The data associated with the events - */ - public log(eventName: string, data: any): void { - if (!this._aiClient && this._aiClientLoaded) { - return; - } else if (!this._aiClient && !this._aiClientLoaded) { - this._telemetryCache.push({ eventName, data }); - return; - } - - data = validateTelemetryData(data); - - // Web does not expect properties and measurements so we must - // spread them out. This is different from desktop which expects them - data = { ...data.properties, ...data.measurements }; - - // undefined assertion is ok since above two if statements cover both cases - this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data); - } - - /** - * Flushes all the telemetry data still in the buffer - */ - public flush(): Promise { - if (this._aiClient) { - this._aiClient.flush(); - this._aiClient = undefined; - } - return Promise.resolve(undefined); - } -} diff --git a/src/vs/platform/telemetry/common/1dsAppender.ts b/src/vs/platform/telemetry/common/1dsAppender.ts index e2ea5b42fdc..ba7cac9d693 100644 --- a/src/vs/platform/telemetry/common/1dsAppender.ts +++ b/src/vs/platform/telemetry/common/1dsAppender.ts @@ -7,11 +7,12 @@ import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-cor import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsoft/1ds-post-js'; import { onUnexpectedError } from 'vs/base/common/errors'; import { mixin } from 'vs/base/common/objects'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0'; -async function getClient(instrumentationKey: string, xhrOverride?: IXHROverride): Promise { +async function getClient(instrumentationKey: string, addInternalFlag?: boolean, xhrOverride?: IXHROverride): Promise { const oneDs = await import('@microsoft/1ds-core-js'); const postPlugin = await import('@microsoft/1ds-post-js'); const appInsightsCore = new oneDs.AppInsightsCore(); @@ -43,10 +44,12 @@ async function getClient(instrumentationKey: string, xhrOverride?: IXHROverride) appInsightsCore.initialize(coreConfig, []); appInsightsCore.addTelemetryInitializer((envelope) => { - envelope['ext'] = envelope['ext'] ?? {}; - envelope['ext']['utc'] = envelope['ext']['utc'] ?? {}; - // Sets it to be internal only based on Windows UTC flagging - envelope['ext']['utc']['flags'] = 0x0000811ECD; + if (addInternalFlag) { + envelope['ext'] = envelope['ext'] ?? {}; + envelope['ext']['utc'] = envelope['ext']['utc'] ?? {}; + // Sets it to be internal only based on Windows UTC flagging + envelope['ext']['utc']['flags'] = 0x0000811ECD; + } }); return appInsightsCore; @@ -60,6 +63,7 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende protected readonly endPointUrl = endpointUrl; constructor( + private readonly _configurationService: IConfigurationService | undefined, private _eventPrefix: string, private _defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing @@ -88,7 +92,8 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende } if (!this._asyncAiCore) { - this._asyncAiCore = getClient(this._aiCoreOrKey, this._xhrOverride); + const isInternal = this._configurationService?.getValue('telemetry.internalTesting'); + this._asyncAiCore = getClient(this._aiCoreOrKey, isInternal, this._xhrOverride); } this._asyncAiCore.then( diff --git a/src/vs/platform/telemetry/node/1dsAppender.ts b/src/vs/platform/telemetry/node/1dsAppender.ts index 5721843fac1..c7b6442cf7a 100644 --- a/src/vs/platform/telemetry/node/1dsAppender.ts +++ b/src/vs/platform/telemetry/node/1dsAppender.ts @@ -6,12 +6,14 @@ import type { AppInsightsCore } from '@microsoft/1ds-core-js'; import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js'; import * as https from 'https'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender'; export class OneDataSystemAppender extends AbstractOneDataSystemAppender { constructor( + configurationService: IConfigurationService | undefined, eventPrefix: string, defaultData: { [key: string]: any } | null, iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing @@ -46,6 +48,6 @@ export class OneDataSystemAppender extends AbstractOneDataSystemAppender { } }; - super(eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride); + super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride); } } diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts deleted file mode 100644 index d5fc5987438..00000000000 --- a/src/vs/platform/telemetry/node/appInsightsAppender.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import type { TelemetryClient } from 'applicationinsights'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { mixin } from 'vs/base/common/objects'; -import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils'; - -async function getClient(aiKey: string): Promise { - const appInsights = await import('applicationinsights'); - let client: TelemetryClient; - if (appInsights.defaultClient) { - client = new appInsights.TelemetryClient(aiKey); - client.channel.setUseDiskRetryCaching(true); - } else { - appInsights.setup(aiKey) - .setAutoCollectRequests(false) - .setAutoCollectPerformance(false) - .setAutoCollectExceptions(false) - .setAutoCollectDependencies(false) - .setAutoDependencyCorrelation(false) - .setAutoCollectConsole(false) - .setInternalLogging(false, false) - .setUseDiskRetryCaching(true) - .start(); - client = appInsights.defaultClient; - } - - if (aiKey.indexOf('AIF-') === 0) { - client.config.endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1'; - } - return client; -} - - -export class AppInsightsAppender implements ITelemetryAppender { - - private _aiClient: string | TelemetryClient | undefined; - private _asyncAIClient: Promise | null; - - constructor( - private _eventPrefix: string, - private _defaultData: { [key: string]: any } | null, - aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing - ) { - if (!this._defaultData) { - this._defaultData = Object.create(null); - } - - if (typeof aiKeyOrClientFactory === 'function') { - this._aiClient = aiKeyOrClientFactory(); - } else { - this._aiClient = aiKeyOrClientFactory; - } - this._asyncAIClient = null; - } - - private _withAIClient(callback: (aiClient: TelemetryClient) => void): void { - if (!this._aiClient) { - return; - } - - if (typeof this._aiClient !== 'string') { - callback(this._aiClient); - return; - } - - if (!this._asyncAIClient) { - this._asyncAIClient = getClient(this._aiClient); - } - - this._asyncAIClient.then( - (aiClient) => { - callback(aiClient); - }, - (err) => { - onUnexpectedError(err); - console.error(err); - } - ); - } - - log(eventName: string, data?: any): void { - if (!this._aiClient) { - return; - } - data = mixin(data, this._defaultData); - data = validateTelemetryData(data); - - // Attemps to suppress https://github.com/microsoft/vscode/issues/140624 - try { - this._withAIClient((aiClient) => aiClient.trackEvent({ - name: this._eventPrefix + '/' + eventName, - properties: data.properties, - measurements: data.measurements - })); - } catch { } - } - - flush(): Promise { - if (this._aiClient) { - return new Promise(resolve => { - this._withAIClient((aiClient) => { - // Attempts to suppress https://github.com/microsoft/vscode/issues/140624 - try { - aiClient.flush({ - callback: () => { - // all data flushed - this._aiClient = undefined; - resolve(undefined); - } - }); - } catch { } - }); - }); - } - return Promise.resolve(undefined); - } -} diff --git a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts b/src/vs/platform/telemetry/test/electron-browser/1dsAppender.test.ts similarity index 85% rename from src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts rename to src/vs/platform/telemetry/test/electron-browser/1dsAppender.test.ts index 09f1f611da9..3b734414796 100644 --- a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/1dsAppender.test.ts @@ -2,23 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Contracts, TelemetryClient } from 'applicationinsights'; +import { AppInsightsCore } from '@microsoft/1ds-core-js'; import * as assert from 'assert'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -class AppInsightsMock extends TelemetryClient { +class AppInsightsCoreMock extends AppInsightsCore { public override config: any; - public override channel: any; - public events: Contracts.EventTelemetry[] = []; + public events: any[] = []; public IsTrackingPageView: boolean = false; public exceptions: any[] = []; constructor() { - super('testKey'); + super(); } - public override trackEvent(event: any) { - this.events.push(event); + public override track(event: any) { + this.events.push(event.baseData); } public override flush(options: any): void { @@ -27,14 +26,14 @@ class AppInsightsMock extends TelemetryClient { } suite('AIAdapter', () => { - let appInsightsMock: AppInsightsMock; - let adapter: AppInsightsAppender; + let appInsightsMock: AppInsightsCoreMock; + let adapter: OneDataSystemAppender; const prefix = 'prefix'; setup(() => { - appInsightsMock = new AppInsightsMock(); - adapter = new AppInsightsAppender(prefix, undefined!, () => appInsightsMock); + appInsightsMock = new AppInsightsCoreMock(); + adapter = new OneDataSystemAppender(undefined, prefix, undefined!, () => appInsightsMock); }); teardown(() => { @@ -49,7 +48,7 @@ suite('AIAdapter', () => { }); test('addional data', () => { - adapter = new AppInsightsAppender(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock); + adapter = new OneDataSystemAppender(undefined, prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock); adapter.log('testEvent'); assert.strictEqual(appInsightsMock.events.length, 1); diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index d398dccd0ac..1cfbab58a30 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -198,7 +198,7 @@ export interface ITerminalCommand { executedMarker?: IXtermMarker; commandStartLineContent?: string; getOutput(): string | undefined; - hasOutput: boolean; + hasOutput(): boolean; genericMarkProperties?: IGenericMarkProperties; } diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index b7b62ad5439..7194815abb1 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -457,7 +457,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { cwd: this._cwd, exitCode: this._exitCode, commandStartLineContent: this._currentCommand.commandStartLineContent, - hasOutput: !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line), + hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line), getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), genericMarkProperties: options?.genericMarkProperties }; @@ -579,7 +579,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { cwd: e.cwd, commandStartLineContent: e.commandStartLineContent, exitCode: e.exitCode, - hasOutput: !!(executedMarker && endMarker && executedMarker.line < endMarker.line), + hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker.line < endMarker.line), getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), genericMarkProperties: e.genericMarkProperties }; @@ -602,7 +602,7 @@ function getOutputForCommand(executedMarker: IMarker | undefined, endMarker: IMa } let output = ''; for (let i = startLine; i < endLine; i++) { - output += buffer.getLine(i)?.translateToString() + '\n'; + output += buffer.getLine(i)?.translateToString(true) + '\n'; } return output === '' ? undefined : output; } diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index d77dce7ecc6..9fcfaaae521 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -40,6 +40,8 @@ export const enum TerminalSettingId { DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx', DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows', UseWslProfiles = 'terminal.integrated.useWslProfiles', + TabsDefaultColor = 'terminal.integrated.tabs.defaultColor', + TabsDefaultIcon = 'terminal.integrated.tabs.defaultIcon', TabsEnabled = 'terminal.integrated.tabs.enabled', TabsEnableAnimation = 'terminal.integrated.tabs.enableAnimation', TabsHideCondition = 'terminal.integrated.tabs.hideCondition', @@ -181,11 +183,6 @@ export enum TitleEventSource { export type ITerminalsLayoutInfo = IRawTerminalsLayoutInfo; export type ITerminalsLayoutInfoById = IRawTerminalsLayoutInfo; -export interface IRawTerminalInstanceLayoutInfo { - relativeSize: number; - terminal: T; -} - export enum TerminalIpcChannels { /** * Communicates between the renderer process and shared process. @@ -318,6 +315,7 @@ export interface IPtyService extends IPtyHostController { getProfiles?(workspaceId: string, profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise; getEnvironment(): Promise; getWslPath(original: string): Promise; + getRevivedPtyNewId(id: number): Promise; setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise; getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise; reduceConnectionGraceTime(): Promise; @@ -464,7 +462,7 @@ export interface IShellLaunchConfig { /** * This is a terminal that attaches to an already running terminal. */ - attachPersistentProcess?: { id: number; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections }; + attachPersistentProcess?: { id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections }; /** * Whether the terminal process environment should be exactly as provided in diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index 9dc40ebec2c..45bad3159a1 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -12,6 +12,27 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionTerminalProfile, ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { createProfileSchemaEnums } from 'vs/platform/terminal/common/terminalProfiles'; +export const terminalColorSchema: IJSONSchema = { + type: ['string', 'null'], + enum: [ + 'terminal.ansiBlack', + 'terminal.ansiRed', + 'terminal.ansiGreen', + 'terminal.ansiYellow', + 'terminal.ansiBlue', + 'terminal.ansiMagenta', + 'terminal.ansiCyan', + 'terminal.ansiWhite' + ], + default: null +}; + +export const terminalIconSchema: IJSONSchema = { + type: 'string', + enum: Array.from(Codicon.getAll(), icon => icon.id), + markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), +}; + const terminalProfileBaseProperties: IJSONSchemaMap = { args: { description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'), @@ -25,25 +46,12 @@ const terminalProfileBaseProperties: IJSONSchemaMap = { type: 'boolean' }, icon: { - description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'), - type: 'string', - enum: Array.from(Codicon.getAll(), icon => icon.id), - markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), + description: localize('terminalProfile.icon', 'A codicon ID to associate with the terminal icon.'), + ...terminalIconSchema }, color: { - description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'), - type: ['string', 'null'], - enum: [ - 'terminal.ansiBlack', - 'terminal.ansiRed', - 'terminal.ansiGreen', - 'terminal.ansiYellow', - 'terminal.ansiBlue', - 'terminal.ansiMagenta', - 'terminal.ansiCyan', - 'terminal.ansiWhite' - ], - default: null + description: localize('terminalProfile.color', 'A theme color ID to associate with the terminal icon.'), + ...terminalColorSchema }, env: { markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."), diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 39c85afcd72..7a07b5b1b83 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -308,13 +308,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return false; } - const [command, hoverMessage, disableCommandStorage] = data.split(';'); + const [command] = data.split(';'); switch (command) { case ITermOscPt.SetMark: { - this._createOrGetCommandDetection(this._terminal).handleGenericCommand({ genericMarkProperties: { hoverMessage: hoverMessage || '', disableCommandStorage: disableCommandStorage === 'true' ? true : false } }); + this._createOrGetCommandDetection(this._terminal).handleGenericCommand({ genericMarkProperties: { disableCommandStorage: true } }); } } - // Unrecognized sequence return false; } diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 5af2d312b6b..3564d49efb4 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -296,6 +296,10 @@ export class PtyHostService extends Disposable implements IPtyService { return this._proxy.getWslPath(original); } + getRevivedPtyNewId(id: number): Promise { + return this._proxy.getRevivedPtyNewId(id); + } + setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { return this._proxy.setTerminalLayoutInfo(args); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 73a62b44cf7..bdecef41367 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -337,6 +337,15 @@ export class PtyService extends Disposable implements IPtyService { }); } + async getRevivedPtyNewId(id: number): Promise { + try { + return this._revivedPtyIdMap.get(id)?.newId; + } catch (e) { + this._logService.trace(`Couldn't find terminal ID ${id}`, e.message); + } + return undefined; + } + async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { this._workspaceLayoutInfos.set(args.workspaceId, args); } diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 39d0df3d6ba..89a181cda49 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { hash } from 'vs/base/common/hash'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; @@ -16,8 +15,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { isWeb } from 'vs/base/common/platform'; /** * Flags to indicate whether to use the default profile or not. @@ -45,8 +42,6 @@ export interface IUserDataProfile { readonly useDefaultFlags?: UseDefaultProfileFlags; } -export type CustomUserDataProfile = IUserDataProfile & { readonly extensionsResource: URI; readonly isDefault: false }; - export function isUserDataProfile(thing: unknown): thing is IUserDataProfile { const candidate = thing as IUserDataProfile | undefined; @@ -65,21 +60,11 @@ export function isUserDataProfile(thing: unknown): thing is IUserDataProfile { } export const PROFILES_ENABLEMENT_CONFIG = 'workbench.experimental.settingsProfiles.enabled'; -export const PROFILES_ENABLEMENT_CONFIG_SCHEMA: IConfigurationPropertySchema = { - 'type': 'boolean', - 'default': false, - 'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."), - scope: ConfigurationScope.APPLICATION -}; -if (!isWeb) { - // Registering here so that the configuration is read properly in main and cli processes. - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - 'properties': { - [PROFILES_ENABLEMENT_CONFIG]: PROFILES_ENABLEMENT_CONFIG_SCHEMA - } - }); -} +export type EmptyWindowWorkspaceIdentifier = 'empty-window'; +export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | EmptyWindowWorkspaceIdentifier; + +export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly all: IUserDataProfile[] }; export const IUserDataProfilesService = createDecorator('IUserDataProfilesService'); export interface IUserDataProfilesService { @@ -88,13 +73,12 @@ export interface IUserDataProfilesService { readonly profilesHome: URI; readonly defaultProfile: IUserDataProfile; - readonly onDidChangeProfiles: Event; + readonly onDidChangeProfiles: Event; readonly profiles: IUserDataProfile[]; - newProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile; - createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise; - setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise; - getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile; + createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise; + setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise; + getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile; removeProfile(profile: IUserDataProfile): Promise; } @@ -115,9 +99,9 @@ export function reviveProfile(profile: UriDto, scheme: string) export const EXTENSIONS_RESOURCE_NAME = 'extensions.json'; -export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile { +export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags): IUserDataProfile { return { - id: hash(location.toString()).toString(16), + id: hash(location.path).toString(16), name: name, location: location, isDefault: false, @@ -136,11 +120,11 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf readonly profilesHome: URI; - private readonly _defaultProfile = this.createDefaultUserDataProfile(false); - get defaultProfile(): IUserDataProfile { return this.profiles[0] ?? this._defaultProfile; } - get profiles(): IUserDataProfile[] { return []; } + get defaultProfile(): IUserDataProfile { return this.profiles[0]; } + protected _profiles: IUserDataProfile[] = [this.createDefaultUserDataProfile(false)]; + get profiles(): IUserDataProfile[] { return this._profiles; } - protected readonly _onDidChangeProfiles = this._register(new Emitter()); + protected readonly _onDidChangeProfiles = this._register(new Emitter()); readonly onDidChangeProfiles = this._onDidChangeProfiles.event; constructor( @@ -152,17 +136,13 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf this.profilesHome = joinPath(this.environmentService.userRoamingDataHome, 'profiles'); } - newProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile { - return toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags); - } - protected createDefaultUserDataProfile(extensions: boolean): IUserDataProfile { const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); return { ...profile, isDefault: true, extensionsResource: extensions ? profile.extensionsResource : undefined }; } - createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { throw new Error('Not implemented'); } - setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { throw new Error('Not implemented'); } - getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } + createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise { throw new Error('Not implemented'); } + setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise { throw new Error('Not implemented'); } + getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } removeProfile(profile: IUserDataProfile): Promise { throw new Error('Not implemented'); } } diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index 3a7b9503f80..e52855d4867 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -4,17 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, reviveProfile, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, WorkspaceIdentifier, UseDefaultProfileFlags, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { Promises } from 'vs/base/common/async'; -import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { joinPath } from 'vs/base/common/resources'; +import { hash } from 'vs/base/common/hash'; export type WillCreateProfileEvent = { profile: IUserDataProfile; @@ -28,21 +30,11 @@ export type WillRemoveProfileEvent = { export const IUserDataProfilesMainService = refineServiceDecorator(IUserDataProfilesService); export interface IUserDataProfilesMainService extends IUserDataProfilesService { + unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise; readonly onWillCreateProfile: Event; readonly onWillRemoveProfile: Event; } -type StoredUserDataProfile = { - name: string; - location: URI; - useDefaultFlags?: UseDefaultProfileFlags; -}; - -type StoredWorkspaceInfo = { - workspace: URI; - profile: URI; -}; - export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService { private readonly _onWillCreateProfile = this._register(new Emitter()); @@ -61,18 +53,16 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme super(stateMainService, uriIdentityService, environmentService, fileService, logService); } - override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + override async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise { if (!this.enabled) { throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); } - profile = reviveProfile(profile, this.profilesHome.scheme); - if (this.getStoredProfiles().some(p => p.name === profile.name)) { - throw new Error(`Profile with name ${profile.name} already exists`); + if (this.getStoredProfiles().some(p => p.name === name)) { + throw new Error(`Profile with name ${name} already exists`); } - if (!(await this.fileService.exists(this.profilesHome))) { - await this.fileService.createFolder(this.profilesHome); - } + const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags); + await this.fileService.createFolder(profile.location); const joiners: Promise[] = []; this._onWillCreateProfile.fire({ @@ -83,39 +73,45 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme }); await Promises.settled(joiners); - const storedProfile: StoredUserDataProfile = { name: profile.name, location: profile.location, useDefaultFlags: profile.useDefaultFlags }; - const storedProfiles = [...this.getStoredProfiles(), storedProfile]; - this.setStoredProfiles(storedProfiles); + this.updateProfiles([profile], []); + if (workspaceIdentifier) { await this.setProfileForWorkspace(profile, workspaceIdentifier); } - return this.profilesObject.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!; + + return profile; } - override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + override async setProfileForWorkspace(profileToSet: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise { if (!this.enabled) { throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); } - profile = reviveProfile(profile, this.profilesHome.scheme); - const workspace = this.getWorkspace(workspaceIdentifier); - const storedWorkspaceInfos = this.getStoredWorskpaceInfos().filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace)); - if (!profile.isDefault) { - storedWorkspaceInfos.push({ workspace, profile: profile.location }); + + const profile = this.profiles.find(p => p.id === profileToSet.id); + if (!profile) { + throw new Error(`Profile '${profileToSet.name}' does not exist`); } - this.setStoredWorskpaceInfos(storedWorkspaceInfos); - return this.profilesObject.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!; + + this.updateWorkspaceAssociation(workspaceIdentifier, profile); } - override async removeProfile(profile: IUserDataProfile): Promise { + async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise { if (!this.enabled) { throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); } - if (profile.isDefault) { + this.updateWorkspaceAssociation(workspaceIdentifier); + } + + override async removeProfile(profileToRemove: IUserDataProfile): Promise { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + if (profileToRemove.isDefault) { throw new Error('Cannot remove default profile'); } - profile = reviveProfile(profile, this.profilesHome.scheme); - if (!this.getStoredProfiles().some(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))) { - throw new Error(`Profile with name ${profile.name} does not exist`); + const profile = this.profiles.find(p => p.id === profileToRemove.id); + if (!profile) { + throw new Error(`Profile '${profileToRemove.name}' does not exist`); } const joiners: Promise[] = []; @@ -127,11 +123,20 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme }); await Promises.settled(joiners); - this.setStoredWorskpaceInfos(this.getStoredWorskpaceInfos().filter(p => !this.uriIdentityService.extUri.isEqual(p.profile, profile.location))); - this.setStoredProfiles(this.getStoredProfiles().filter(p => !this.uriIdentityService.extUri.isEqual(p.location, profile.location))); + if (profile.id === this.profilesObject.emptyWindow?.id) { + this.profilesObject.emptyWindow = undefined; + } + for (const workspace of [...this.profilesObject.workspaces.keys()]) { + if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) { + this.profilesObject.workspaces.delete(workspace); + } + } + this.saveStoredProfileAssociations(); + + this.updateProfiles([], [profile]); try { - if (this.profiles.length === 2) { + if (this.profiles.length === 1) { await this.fileService.del(this.profilesHome, { recursive: true }); } else { await this.fileService.del(profile.location, { recursive: true }); @@ -141,15 +146,62 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme } } - private setStoredProfiles(storedProfiles: StoredUserDataProfile[]) { + private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) { + const storedProfiles: StoredUserDataProfile[] = []; + for (const profile of [...this.profilesObject.profiles, ...added]) { + if (profile.isDefault) { + continue; + } + if (removed.some(p => profile.id === p.id)) { + continue; + } + storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags }); + } this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles); this._profilesObject = undefined; - this._onDidChangeProfiles.fire(this.profiles); + this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); } - private setStoredWorskpaceInfos(storedWorkspaceInfos: StoredWorkspaceInfo[]) { - this.stateMainService.setItem(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, storedWorkspaceInfos); + private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) { + const workspace = this.getWorkspace(workspaceIdentifier); + + // Folder or Multiroot workspace + if (URI.isUri(workspace)) { + this.profilesObject.workspaces.delete(workspace); + if (newProfile && !newProfile.isDefault) { + this.profilesObject.workspaces.set(workspace, newProfile); + } + } + // Empty Window + else { + this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined; + } + + this.saveStoredProfileAssociations(); + } + + private saveStoredProfileAssociations() { + const workspaces: IStringDictionary = {}; + for (const [workspace, profile] of this.profilesObject.workspaces.entries()) { + workspaces[workspace.toString()] = profile.location.toString(); + } + const emptyWindow = this.profilesObject.emptyWindow?.location.toString(); + this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces, emptyWindow }); this._profilesObject = undefined; } + protected override getStoredProfileAssociations(): StoredProfileAssociations { + const oldKey = 'workspaceAndProfileInfo'; + const storedWorkspaceInfos = this.stateMainService.getItem<{ workspace: UriComponents; profile: UriComponents }[]>(oldKey, undefined); + if (storedWorkspaceInfos) { + this.stateMainService.removeItem(oldKey); + const workspaces = storedWorkspaceInfos.reduce>((result, { workspace, profile }) => { + result[URI.revive(workspace).toString()] = URI.revive(profile).toString(); + return result; + }, {}); + this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces }); + } + return super.getStoredProfileAssociations(); + } + } diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts index 97ae03b8663..1666539da3e 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts @@ -9,14 +9,13 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; -import { IUserDataProfile, IUserDataProfilesService, reviveProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, reviveProfile, UseDefaultProfileFlags, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; export class UserDataProfilesNativeService extends UserDataProfilesService implements IUserDataProfilesService { private readonly channel: IChannel; - private _profiles: IUserDataProfile[] = []; override get profiles(): IUserDataProfile[] { return this._profiles; } constructor( @@ -29,20 +28,21 @@ export class UserDataProfilesNativeService extends UserDataProfilesService imple super(environmentService, fileService, logService); this.channel = mainProcessService.getChannel('userDataProfiles'); this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme)); - this._register(this.channel.listen('onDidChangeProfiles')((profiles) => { - this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme)); - this._onDidChangeProfiles.fire(this._profiles); + this._register(this.channel.listen('onDidChangeProfiles')(e => { + const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme)); + const removed = e.removed.map(profile => reviveProfile(profile, this.profilesHome.scheme)); + this._profiles = e.all.map(profile => reviveProfile(profile, this.profilesHome.scheme)); + this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); })); } - override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { - const result = await this.channel.call>('createProfile', [profile, workspaceIdentifier]); + override async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + const result = await this.channel.call>('createProfile', [name, useDefaultFlags, workspaceIdentifier]); return reviveProfile(result, this.profilesHome.scheme); } - override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { - const result = await this.channel.call>('setProfileForWorkspace', [profile, workspaceIdentifier]); - return reviveProfile(result, this.profilesHome.scheme); + override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { + await this.channel.call>('setProfileForWorkspace', [profile, workspaceIdentifier]); } override removeProfile(profile: IUserDataProfile): Promise { diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts index 1a7f69d3290..34428dd9689 100644 --- a/src/vs/platform/userDataProfile/node/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IStringDictionary } from 'vs/base/common/collections'; import { ResourceMap } from 'vs/base/common/map'; import { revive } from 'vs/base/common/marshalling'; import { UriDto } from 'vs/base/common/types'; @@ -12,29 +13,30 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile, WorkspaceIdentifier, EmptyWindowWorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -type UserDataProfilesObject = { +export type UserDataProfilesObject = { profiles: IUserDataProfile[]; workspaces: ResourceMap; + emptyWindow?: IUserDataProfile; }; -type StoredUserDataProfile = { +export type StoredUserDataProfile = { name: string; location: URI; useDefaultFlags?: UseDefaultProfileFlags; }; -type StoredWorkspaceInfo = { - workspace: URI; - profile: URI; +export type StoredProfileAssociations = { + workspaces?: IStringDictionary; + emptyWindow?: string; }; export class UserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService { protected static readonly PROFILES_KEY = 'userDataProfiles'; - protected static readonly WORKSPACE_PROFILE_INFO_KEY = 'workspaceAndProfileInfo'; + protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations'; protected enabled: boolean = false; @@ -55,42 +57,57 @@ export class UserDataProfilesService extends BaseUserDataProfilesService impleme protected _profilesObject: UserDataProfilesObject | undefined; protected get profilesObject(): UserDataProfilesObject { - if (!this.enabled) { - return { profiles: [], workspaces: new ResourceMap() }; - } if (!this._profilesObject) { - const profiles = this.getStoredProfiles().map(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)); + const profiles = this.enabled ? this.getStoredProfiles().map(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : []; + let emptyWindow: IUserDataProfile | undefined; const workspaces = new ResourceMap(); if (profiles.length) { - profiles.unshift(this.createDefaultUserDataProfile(true)); - for (const workspaceProfileInfo of this.getStoredWorskpaceInfos()) { - const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, workspaceProfileInfo.profile)); - if (profile) { - workspaces.set(workspaceProfileInfo.workspace, profile); + const profileAssicaitions = this.getStoredProfileAssociations(); + if (profileAssicaitions.workspaces) { + for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) { + const workspace = URI.parse(workspacePath); + const profileLocation = URI.parse(profilePath); + const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation)); + if (profile) { + workspaces.set(workspace, profile); + } } } + if (profileAssicaitions.emptyWindow) { + const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow); + emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation)); + } } - this._profilesObject = { profiles, workspaces }; + profiles.unshift(this.createDefaultUserDataProfile(profiles.length > 0)); + this._profilesObject = { profiles, workspaces, emptyWindow }; } return this._profilesObject; } override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; } - override getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile { - return this.profilesObject.workspaces.get(this.getWorkspace(workspaceIdentifier)) ?? this.defaultProfile; + override getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { + const workspace = this.getWorkspace(workspaceIdentifier); + const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow; + return profile ?? this.defaultProfile; } - protected getWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier) { - return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) ? workspaceIdentifier.uri : workspaceIdentifier.configPath; + protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier { + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return workspaceIdentifier.uri; + } + if (isWorkspaceIdentifier(workspaceIdentifier)) { + return workspaceIdentifier.configPath; + } + return 'empty-window'; } protected getStoredProfiles(): StoredUserDataProfile[] { return revive(this.stateService.getItem[]>(UserDataProfilesService.PROFILES_KEY, [])); } - protected getStoredWorskpaceInfos(): StoredWorkspaceInfo[] { - return revive(this.stateService.getItem[]>(UserDataProfilesService.WORKSPACE_PROFILE_INFO_KEY, [])); + protected getStoredProfileAssociations(): StoredProfileAssociations { + return revive(this.stateService.getItem>(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, {})); } } diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index b284dd6fd70..549c686644c 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -54,8 +54,10 @@ suite('UserDataProfileService (Common)', () => { assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined); }); - test('profiles are empty', () => { - assert.deepStrictEqual(testObject.profiles, []); + test('profiles always include default profile', () => { + assert.deepStrictEqual(testObject.profiles.length, 1); + assert.deepStrictEqual(testObject.profiles[0].isDefault, true); + assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined); }); diff --git a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts index b5592746149..23e7656a1ad 100644 --- a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts +++ b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts @@ -54,18 +54,20 @@ suite('UserDataProfileMainService', () => { assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined); }); - test('profiles are empty', () => { - assert.deepStrictEqual(testObject.profiles, []); + test('profiles always include default profile', () => { + assert.deepStrictEqual(testObject.profiles.length, 1); + assert.deepStrictEqual(testObject.profiles[0].isDefault, true); + assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined); }); test('default profile when there are profiles', async () => { - await testObject.createProfile(testObject.newProfile('test')); + await testObject.createProfile('test'); assert.strictEqual(testObject.defaultProfile.isDefault, true); assert.strictEqual(testObject.defaultProfile.extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString()); }); test('default profile when profiles are removed', async () => { - const profile = await testObject.createProfile(testObject.newProfile('test')); + const profile = await testObject.createProfile('test'); await testObject.removeProfile(profile); assert.strictEqual(testObject.defaultProfile.isDefault, true); assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined); diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 1b68d3a166f..39b7fbba2c0 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -135,6 +135,7 @@ export interface IWindowSettings { readonly enableMenuBarMnemonics: boolean; readonly closeWhenEmpty: boolean; readonly clickThroughInactive: boolean; + readonly experimental?: { useSandbox: boolean }; } interface IWindowBorderColors { diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index fdd9954a76a..e6f0513a76d 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -43,8 +43,6 @@ export interface ICodeWindow extends IDisposable { ready(): Promise; setReady(): void; - readonly hasHiddenTitleBarStyle: boolean; - addTabbedWindow(window: ICodeWindow): void; load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void; diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 02524b2235b..216d743026f 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -18,7 +18,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -122,9 +122,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { private _config: INativeWindowConfiguration | undefined; get config(): INativeWindowConfiguration | undefined { return this._config; } - private hiddenTitleBarStyle: boolean | undefined; - get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; } - get isExtensionDevelopmentHost(): boolean { return !!(this._config?.extensionDevelopmentPath); } get isExtensionTestHost(): boolean { return !!(this._config?.extensionTestsPath); } @@ -140,6 +137,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { private representedFilename: string | undefined; private documentEdited: boolean | undefined; + private customTrafficLightPosition: boolean | undefined; + private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = []; private readonly touchBarGroups: TouchBarSegmentedControl[] = []; @@ -206,7 +205,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624 // Refs https://github.com/microsoft/vscode/issues/140098 enableBlinkFeatures: 'HighlightAPI', - ...this.environmentMainService.sandbox ? + ...windowSettings?.experimental?.useSandbox ? // Sandbox { @@ -251,19 +250,21 @@ export class CodeWindow extends Disposable implements ICodeWindow { const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; - this.hiddenTitleBarStyle = true; if (!isMacintosh) { options.frame = false; } if (useWindowControlsOverlay(this.configurationService, this.environmentMainService)) { - // This logic will not perfectly guess the right colors to use on initialization, - // but prefer to keep things simple as it is temporary and not noticeable + + // This logic will not perfectly guess the right colors + // to use on initialization, but prefer to keep things + // simple as it is temporary and not noticeable + const titleBarColor = this.themeMainService.getWindowSplash()?.colorInfo.titleBarBackground ?? this.themeMainService.getBackgroundColor(); const symbolColor = Color.fromHex(titleBarColor).isDarker() ? '#FFFFFF' : '#000000'; options.titleBarOverlay = { - height: 29, // The smallest size of the title bar on windows accounting for the border on windows 11 + height: 29, // the smallest size of the title bar on windows accounting for the border on windows 11 color: titleBarColor, symbolColor }; @@ -277,29 +278,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._id = this._win.id; - // re-position traffic light if command center is visible - if (useCustomTitleStyle && isMacintosh) { - const ccConfigKey = 'window.commandCenter'; - const trafficLightUpdater = () => { - // temporarily disabled because of https://github.com/microsoft/vscode/pull/150272#issuecomment-1152218493 - // const on = this.configurationService.getValue(ccConfigKey); - // if (on) { - // this._win.setTrafficLightPosition({ x: 7, y: 9 }); - // } else { - // this._win.setTrafficLightPosition({ x: 7, y: 6 }); - // } - }; - trafficLightUpdater(); - this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(ccConfigKey)) { - trafficLightUpdater(); - } - }, undefined, this._store); - } - - // Open devtools if instructed from command line args - if (this.environmentMainService.args['open-devtools'] === true) { - this._win.webContents.openDevTools(); + if (isMacintosh && useCustomTitleStyle) { + this.updateTrafficLightPosition(); // adjust traffic light position depending on command center } if (isMacintosh && useCustomTitleStyle) { @@ -348,6 +328,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { } //#endregion + // Open devtools if instructed from command line args + if (this.environmentMainService.args['open-devtools'] === true) { + this._win.webContents.openDevTools(); + } + // respect configured menu bar visibility this.onConfigurationUpdated(); @@ -359,7 +344,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } setRepresentedFilename(filename: string): void { - if (isMacintosh) { + if (isMacintosh && !this.customTrafficLightPosition) { // TODO@electron https://github.com/electron/electron/issues/34822 this._win.setRepresentedFilename(filename); } else { this.representedFilename = filename; @@ -367,7 +352,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } getRepresentedFilename(): string | undefined { - if (isMacintosh) { + if (isMacintosh && !this.customTrafficLightPosition) { // TODO@electron https://github.com/electron/electron/issues/34822 return this._win.getRepresentedFilename(); } @@ -525,7 +510,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); // Handle configuration changes - this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated())); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Handle Workspace events this._register(this.workspacesManagementMainService.onDidDeleteUntitledWorkspace(e => this.onDidDeleteUntitledWorkspace(e))); @@ -542,7 +527,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { private marketplaceHeadersPromise: Promise | undefined; private getMarketplaceHeaders(): Promise { if (!this.marketplaceHeadersPromise) { - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, this.applicationStorageMainService); + this.marketplaceHeadersPromise = resolveMarketplaceHeaders( + this.productService.version, + this.productService, + this.environmentMainService, + this.configurationService, + this.fileService, + this.applicationStorageMainService, + this.telemetryService); } return this.marketplaceHeadersPromise; @@ -728,7 +720,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - private onConfigurationUpdated(): void { + private onConfigurationUpdated(e?: IConfigurationChangeEvent): void { // Menubar const newMenuBarVisibility = this.getMenuBarVisibility(); @@ -737,6 +729,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.setMenuBarVisibility(newMenuBarVisibility); } + // Traffic Lights + this.updateTrafficLightPosition(e); + // Proxy let newHttpProxy = (this.configurationService.getValue('http.proxy') || '').trim() || (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() // Not standardized. @@ -805,10 +800,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.readyState = ReadyState.NAVIGATING; // Load URL - this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ? - 'vs/code/electron-sandbox/workbench/workbench.html' : - 'vs/code/electron-browser/workbench/workbench.html', require - ).toString(true)); + this._win.loadURL(FileAccess.asBrowserUri('vs/code/electron-sandbox/workbench/workbench.html', require).toString(true)); // Remember that we did load const wasLoaded = this.wasLoaded; @@ -906,7 +898,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { configuration.editSessionId = this.environmentMainService.editSessionId; // set latest edit session id configuration.profiles = { all: this.userDataProfilesService.profiles, - current: configuration.workspace ? this.userDataProfilesService.getProfile(configuration.workspace) : this.userDataProfilesService.defaultProfile, + current: this.userDataProfilesService.getProfile(configuration.workspace ?? 'empty-window'), }; // Load config @@ -1296,6 +1288,31 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } + private updateTrafficLightPosition(e?: IConfigurationChangeEvent): void { + if (!isMacintosh) { + return; // only applies to macOS + } + + const commandCenterSettingKey = 'window.commandCenter'; + if (e && !e.affectsConfiguration(commandCenterSettingKey)) { + return; + } + + const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; + if (!useCustomTitleStyle) { + return; // only applies with custom title bar + } + + const useCustomTrafficLightPosition = this.configurationService.getValue(commandCenterSettingKey); + if (useCustomTrafficLightPosition) { + this._win.setTrafficLightPosition({ x: 7, y: 9 }); + } else { + this._win.setTrafficLightPosition({ x: 7, y: 6 }); + } + + this.customTrafficLightPosition = useCustomTrafficLightPosition; + } + handleTitleDoubleClick(): void { // Respect system settings on mac with regards to title click on windows title diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 37cd6b32c68..0d2cdc09834 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1302,7 +1302,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic profiles: { all: this.userDataProfilesService.profiles, - current: options.workspace ? this.userDataProfilesService.getProfile(options.workspace) : this.userDataProfilesService.defaultProfile, + current: this.userDataProfilesService.getProfile(options.workspace ?? 'empty-window'), }, homeDir: this.environmentMainService.userHome.fsPath, diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index b17048b3ad2..827801bf891 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -49,7 +49,6 @@ suite('WindowsFinder', () => { lastFocusTime = options.lastFocusTime; isFullScreen = false; isReady = true; - hasHiddenTitleBarStyle = false; ready(): Promise { throw new Error('Method not implemented.'); } setReady(): void { throw new Error('Method not implemented.'); } diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index 3801775bee0..2f29d96f1ab 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -377,7 +377,10 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa }); try { - app.setJumpList(jumpList); + const res = app.setJumpList(jumpList); + if (res && res !== 'ok') { + this.logService.warn(`updateWindowsJumpList#setJumpList unexpected result: ${res}`); + } } catch (error) { this.logService.warn('updateWindowsJumpList#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors } diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index deda0faf8d4..2ee6816721e 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -23,6 +23,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; import { findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder'; import { isWorkspaceIdentifier, IWorkspaceIdentifier, IResolvedWorkspace, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, isUntitledWorkspace } from 'vs/platform/workspace/common/workspace'; @@ -75,6 +76,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork constructor( @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, + @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @IProductService private readonly productService: IProductService @@ -203,6 +205,9 @@ export class WorkspacesManagementMainService extends Disposable implements IWork // Delete from disk this.doDeleteUntitledWorkspaceSync(workspace); + // unset workspace from profiles + this.userDataProfilesMainService.unsetWorkspace(workspace); + // Event this._onDidDeleteUntitledWorkspace.fire(workspace); } diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index 25fb2b4a852..197938f32e4 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -20,9 +20,13 @@ import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { EnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; +import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; +import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { IRawFileWorkspaceFolder, IRawUriWorkspaceFolder, IWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspace/common/workspace'; import { IStoredWorkspace, IStoredWorkspaceFolder, IWorkspaceFolderCreationData, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { WorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; @@ -109,7 +113,9 @@ flakySuite('WorkspacesManagementMainService', () => { } }; - service = new WorkspacesManagementMainService(environmentMainService, new NullLogService(), new TestBackupMainService(), new TestDialogMainService(), productService); + const logService = new NullLogService(); + const fileService = new FileService(logService); + service = new WorkspacesManagementMainService(environmentMainService, logService, new UserDataProfilesMainService(new StateMainService(environmentMainService, logService, fileService), new UriIdentityService(fileService), environmentMainService, fileService, logService), new TestBackupMainService(), new TestDialogMainService(), productService); return pfs.Promises.mkdir(untitledWorkspacesHomePath, { recursive: true }); }); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 631a6d18b12..653748724e0 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -12,9 +12,9 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import product from 'vs/platform/product/common/product'; @@ -109,7 +109,7 @@ class CliMain extends Disposable { services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts index 367dcfb7b76..3c11876cfe9 100644 --- a/src/vs/server/node/remoteTerminalChannel.ts +++ b/src/vs/server/node/remoteTerminalChannel.ts @@ -138,6 +138,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< case '$setTerminalLayoutInfo': return this._ptyService.setTerminalLayoutInfo(args); case '$serializeTerminalState': return this._ptyService.serializeTerminalState.apply(this._ptyService, args); case '$reviveTerminalProcesses': return this._ptyService.reviveTerminalProcesses.apply(this._ptyService, args); + case '$getRevivedPtyNewId': return this._ptyService.getRevivedPtyNewId.apply(this._ptyService, args); case '$setUnicodeVersion': return this._ptyService.setUnicodeVersion.apply(this._ptyService, args); case '$reduceConnectionGraceTime': return this._reduceConnectionGraceTime(); case '$updateIcon': return this._ptyService.updateIcon.apply(this._ptyService, args); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index fa2c8004a77..c20bd776c55 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -24,10 +24,10 @@ import { IEncryptionMainService } from 'vs/platform/encryption/common/encryption import { EncryptionMainService } from 'vs/platform/encryption/node/encryptionMainService'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -50,7 +50,6 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { getPiiPathsFromEnvironment, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; @@ -73,6 +72,7 @@ import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerServic import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; const eventPrefix = 'monacoworkbench'; @@ -128,16 +128,16 @@ export async function setupServerServices(connectionToken: ServerConnectionToken // Request services.set(IRequestService, new SyncDescriptor(RequestService)); - let appInsightsAppender: ITelemetryAppender = NullAppender; + let oneDsAppender: ITelemetryAppender = NullAppender; const machineId = await getMachineId(); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.asimovKey) { - appInsightsAppender = new AppInsightsAppender(eventPrefix, null, productService.aiConfig.asimovKey); - disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data + if (productService.aiConfig && productService.aiConfig.ariaKey) { + oneDsAppender = new OneDataSystemAppender(configurationService, eventPrefix, null, productService.aiConfig.ariaKey); + disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } const config: ITelemetryServiceConfig = { - appenders: [appInsightsAppender], + appenders: [oneDsAppender], commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, productService.msftInternalDomains, environmentService.installSourcePath, 'remoteAgent'), piiPaths: getPiiPathsFromEnvironment(environmentService) }; @@ -165,7 +165,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); const instantiationService: IInstantiationService = new InstantiationService(services); services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService)); @@ -188,12 +188,12 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(ICredentialsMainService, new SyncDescriptor(CredentialsWebMainService)); instantiationService.invokeFunction(accessor => { - const extensionManagementService = accessor.get(IExtensionManagementService); + const extensionManagementService = accessor.get(INativeServerExtensionManagementService); const extensionsScannerService = accessor.get(IExtensionsScannerService); const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionManagementCLIService, logService, extensionHostStatusService, extensionsScannerService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); - const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), appInsightsAppender); + const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender); socketServer.registerChannel('telemetry', telemetryChannel); socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService)); @@ -213,7 +213,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel('credentials', credentialsChannel); // clean up deprecated extensions - (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); + extensionManagementService.removeUninstalledExtensions(true); disposables.add(new ErrorTelemetry(accessor.get(ITelemetryService))); diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 6f9a167111d..d48b9079fe7 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -70,7 +70,7 @@ export class MainThreadAuthenticationProvider extends Disposable implements IAut quickPick.onDidAccept(() => { const updatedAllowedList = quickPick.items .map(i => (i as TrustedExtensionsQuickPickItem).extension); - this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.PROFILE, StorageTarget.USER); + this.storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.APPLICATION, StorageTarget.USER); quickPick.dispose(); }); @@ -116,7 +116,7 @@ export class MainThreadAuthenticationProvider extends Disposable implements IAut const removeSessionPromises = sessions.map(session => this.removeSession(session.id)); await Promise.all(removeSessionPromises); removeAccountUsage(this.storageService, this.id, accountName); - this.storageService.remove(`${this.id}-${accountName}`, StorageScope.PROFILE); + this.storageService.remove(`${this.id}-${accountName}`, StorageScope.APPLICATION); } } @@ -201,7 +201,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise { this.authenticationService.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); - this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); } @@ -224,9 +224,9 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (!options.forceNewSession && sessions.length) { if (supportsMultipleAccounts) { if (options.clearSessionPreference) { - this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.PROFILE); + this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.APPLICATION); } else { - const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.PROFILE); + const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.APPLICATION); if (existingSessionPreference) { const matchingSession = sessions.find(session => session.id === existingSessionPreference); if (matchingSession && this.authenticationService.isAccessAllowed(providerId, matchingSession.account.label, extensionId)) { diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts index e3e5d652a16..b47e47a7286 100644 --- a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts +++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts @@ -4,29 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; -import { IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ILogService } from 'vs/platform/log/common/log'; -import { revive } from 'vs/base/common/marshalling'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; -import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto'; -export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { - if (!data?.edits) { +export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto): ResourceEdit[] { + const edits = reviveWorkspaceEditDto(data)?.edits; + if (!edits) { return []; } - - const result: ResourceEdit[] = []; - for (const edit of revive(data).edits) { - if (edit._type === WorkspaceEditType.File) { - result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata)); - } else if (edit._type === WorkspaceEditType.Text) { - result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); - } else if (edit._type === WorkspaceEditType.Cell) { - result.push(new ResourceNotebookCellEdit(edit.resource, NotebookDto.fromCellEditOperationDto(edit.edit), edit.notebookVersionId, edit.metadata)); + return edits.map(edit => { + if (ResourceTextEdit.is(edit)) { + return ResourceTextEdit.lift(edit); } - } - return result; + if (ResourceFileEdit.is(edit)) { + return ResourceFileEdit.lift(edit); + } + if (ResourceNotebookCellEdit.is(edit)) { + return ResourceNotebookCellEdit.lift(edit); + } + throw new Error('Unsupported edit'); + }); } @extHostNamedCustomer(MainContext.MainThreadBulkEdits) diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index b1500fcbe64..0321d2202f5 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -74,6 +74,15 @@ export class MainThreadCommands implements MainThreadCommandsShape { } } + $fireCommandActivationEvent(id: string): void { + const activationEvent = `onCommand:${id}`; + if (!this._extensionService.activationEventIsDone(activationEvent)) { + // this is NOT awaited because we only use it as drive-by-activation + // for commands that are already known inside the extension host + this._extensionService.activateByEvent(activationEvent); + } + } + async $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise { if (args instanceof SerializableObjectWithBuffers) { args = args.value; diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index c0ec9d1b550..1b0e0389246 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -223,6 +223,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb const folderUri = folder ? uri.revive(folder) : undefined; const launch = this.debugService.getConfigurationManager().getLaunch(folderUri); const parentSession = this.getSession(options.parentSessionID); + const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined; const debugOptions: IDebugSessionOptions = { noDebug: options.noDebug, parentSession, @@ -230,11 +231,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb repl: options.repl, compact: options.compact, debugUI: options.debugUI, - compoundRoot: parentSession?.compoundRoot + compoundRoot: parentSession?.compoundRoot, + saveBeforeStart: saveBeforeStart }; try { - const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined; - return this.debugService.startDebugging(launch, nameOrConfig, debugOptions, saveBeforeStart); + return this.debugService.startDebugging(launch, nameOrConfig, debugOptions); } catch (err) { throw new ErrorNoTelemetry(err && err.message ? err.message : 'cannot start debugging'); } diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 1f8066b2c0d..e9ca6d75502 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { MainThreadDiaglogsShape, MainContext, MainThreadDialogOpenOptions, MainThreadDialogSaveOptions } from '../common/extHost.protocol'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { forEach } from 'vs/base/common/collections'; import { IFileDialogService, IOpenDialogOptions, ISaveDialogOptions } from 'vs/platform/dialogs/common/dialogs'; @extHostNamedCustomer(MainContext.MainThreadDialogs) @@ -51,7 +50,9 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { }; if (options?.filters) { result.filters = []; - forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value })); + for (const [key, value] of Object.entries(options.filters)) { + result.filters!.push({ name: key, extensions: value }); + } } return result; } @@ -64,7 +65,9 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { }; if (options?.filters) { result.filters = []; - forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value })); + for (const [key, value] of Object.entries(options.filters)) { + result.filters.push({ name: key, extensions: value }); + } } return result; } diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index edc0ede1509..638fe8aa474 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -415,7 +415,7 @@ export class MainThreadTextEditor { if (!this._codeEditor) { return; } - this._codeEditor.setDecorations('exthost-api', key, ranges); + this._codeEditor.setDecorationsByType('exthost-api', key, ranges); } public setDecorationsFast(key: string, _ranges: number[]): void { @@ -426,7 +426,7 @@ export class MainThreadTextEditor { for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) { ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]); } - this._codeEditor.setDecorationsFast(key, ranges); + this._codeEditor.setDecorationsByTypeFast(key, ranges); } public revealRange(range: IRange, revealType: TextEditorRevealType): void { diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts index 500240311be..2bcb34d00d1 100644 --- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts +++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts @@ -173,7 +173,7 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape { */ private _generateTabId(editor: EditorInput, groupId: number) { let resourceString: string | undefined; - // Properly get the reousrce and account for sideby side editors + // Properly get the resource and account for side by side editors const resource = EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }); if (resource instanceof URI) { resourceString = resource.toString(); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 6086f867f4b..02e75eae05e 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -191,7 +191,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { // our workspace watchers in case the request is a // recursive file watcher. // Still allow for non-recursive watch requests as a way - // to bypass configured exlcude rules though + // to bypass configured exclude rules though // (see https://github.com/microsoft/vscode/issues/146066) if (workspaceFolder && opts.recursive) { this._logService.trace(`MainThreadFileSystem#$watch(): ignoring request to start watching because path is inside workspace (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index 54a2887e33b..c7c01c717bf 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -185,7 +185,10 @@ registerAction2(class ResetMemento extends Action2 { constructor() { super({ id: 'files.participants.resetChoice', - title: localize('label', "Reset choice for 'File operation needs preview'"), + title: { + value: localize('label', "Reset choice for 'File operation needs preview'"), + original: `Reset choice for 'File operation needs preview'` + }, f1: true }); } diff --git a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts index 75296f0373e..861c1e680e2 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebookEditors.ts @@ -132,7 +132,7 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape if (notebookEditor) { return notebookEditor.getId(); } else { - throw new Error(`Notebook Editor creation failure for documenet ${resource}`); + throw new Error(`Notebook Editor creation failure for document ${resource}`); } } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 60cb8a79824..be081d8e61f 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as Types from 'vs/base/common/types'; import * as Platform from 'vs/base/common/platform'; -import { IStringDictionary, forEach } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -342,7 +342,7 @@ namespace TaskDTO { return result; } - export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id: string; color?: string }): ContributedTask | undefined { + export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id?: string; color?: string }, hide?: boolean): ContributedTask | undefined { if (!task || (typeof task.name !== 'string')) { return undefined; } @@ -383,7 +383,8 @@ namespace TaskDTO { isBackground: !!task.isBackground, problemMatchers: task.problemMatchers.slice(), detail: task.detail, - icon + icon, + hide } ); return result; @@ -433,7 +434,9 @@ export class MainThreadTask implements MainThreadTaskShape { let resolvedDefinition: ITaskDefinitionDTO = execution.task!.definition; if (execution.task?.execution && CustomExecutionDTO.is(execution.task.execution) && event.resolvedVariables) { const dictionary: IStringDictionary = {}; - Array.from(event.resolvedVariables.entries()).forEach(entry => dictionary[entry[0]] = entry[1]); + for (const [key, value] of event.resolvedVariables.entries()) { + dictionary[key] = value; + } resolvedDefinition = await this._configurationResolverService.resolveAnyAsync(task.getWorkspaceFolder(), execution.task.definition, dictionary); } @@ -449,9 +452,9 @@ export class MainThreadTask implements MainThreadTaskShape { } public dispose(): void { - this._providers.forEach((value) => { + for (const value of this._providers.values()) { value.disposable.dispose(); - }); + } this._providers.clear(); } @@ -492,7 +495,7 @@ export class MainThreadTask implements MainThreadTaskShape { dto.name = ((dto.name === undefined) ? '' : dto.name); // Using an empty name causes the name to default to the one given by the provider. return Promise.resolve(this._proxy.$resolveTask(handle, dto)).then(resolvedTask => { if (resolvedTask) { - return TaskDTO.to(resolvedTask, this._workspaceContextServer, true, task.configurationProperties.icon); + return TaskDTO.to(resolvedTask, this._workspaceContextServer, true, task.configurationProperties.icon, task.configurationProperties.hide); } return undefined; @@ -679,10 +682,7 @@ export class MainThreadTask implements MainThreadTaskShape { const vars: string[] = []; toResolve.variables.forEach(item => vars.push(item)); return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => { - const partiallyResolvedVars = new Array(); - forEach(values.variables, (entry) => { - partiallyResolvedVars.push(entry.value); - }); + const partiallyResolvedVars = Array.from(Object.values(values.variables)); return new Promise((resolve, reject) => { this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => { if (!resolvedVars) { diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index b96d10e4131..6759edbcd59 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -288,7 +288,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape const proxy = request.proxy; this._terminalProcessProxies.set(proxy.instanceId, proxy); - // Note that onReisze is not being listened to here as it needs to fire when max dimensions + // Note that onResize is not being listened to here as it needs to fire when max dimensions // change, excluding the dimension override const initialDimensions: ITerminalDimensionsDto | undefined = request.cols && request.rows ? { columns: request.cols, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 1932e2ce93d..b94fab104e1 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; -import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; @@ -322,16 +321,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution { let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length; let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1; for (const { value, collector, description } of extensionPoints) { - forEach(value, entry => { - if (!this.isValidViewsContainer(entry.value, collector)) { + Object.entries(value).forEach(([key, value]) => { + if (!this.isValidViewsContainer(value, collector)) { return; } - switch (entry.key) { + switch (key) { case 'activitybar': - activityBarOrder = this.registerCustomViewContainers(entry.value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar); + activityBarOrder = this.registerCustomViewContainers(value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar); break; case 'panel': - panelOrder = this.registerCustomViewContainers(entry.value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel); + panelOrder = this.registerCustomViewContainers(value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel); break; } }); @@ -455,22 +454,22 @@ class ViewsExtensionHandler implements IWorkbenchContribution { for (const extension of extensions) { const { value, collector } = extension; - forEach(value, entry => { - if (!this.isValidViewDescriptors(entry.value, collector)) { + Object.entries(value).forEach(([key, value]) => { + if (!this.isValidViewDescriptors(value, collector)) { return; } - if (entry.key === 'remote' && !isProposedApiEnabled(extension.description, 'contribViewsRemote')) { - collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"contribViewsRemote\"]' to be added to 'Remote'.", entry.key)); + if (key === 'remote' && !isProposedApiEnabled(extension.description, 'contribViewsRemote')) { + collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"contribViewsRemote\"]' to be added to 'Remote'.", key)); return; } - const viewContainer = this.getViewContainer(entry.key); + const viewContainer = this.getViewContainer(key); if (!viewContainer) { - collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); + collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", key)); } const container = viewContainer || this.getDefaultViewContainer(); - const viewDescriptors = coalesce(entry.value.map((item, index) => { + const viewDescriptors = coalesce(value.map((item, index) => { // validate if (viewIds.has(item.id)) { collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}`", item.id)); @@ -514,7 +513,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed, order: order, extensionId: extension.description.identifier, - originalContainerId: entry.key, + originalContainerId: key, group: item.group, remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated hideByDefault: initialVisibility === InitialVisibility.Hidden, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d2240a26fbd..6f411f31a82 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -92,6 +92,7 @@ import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { combinedDisposable } from 'vs/base/common/lifecycle'; import { checkProposedApiEnabled, ExtensionIdentifierSet, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -182,7 +183,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands)); const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands)); + rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); // Check that no named customers are missing const expected = Object.values>(ExtHostContext); @@ -392,6 +393,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extensions: typeof vscode.extensions = { getExtension(extensionId: string, includeFromDifferentExtensionHosts?: boolean): vscode.Extension | undefined { + if (equalsIgnoreCase(extensionId, 'ms-vscode.references-view')) { + extHostApiDeprecation.report(`The extension 'ms-vscode.references-view' has been renamed.`, extension, `Use 'vscode.references-view' instead.`); + extensionId = 'vscode.references-view'; + } + if (!isProposedApiEnabled(extension, 'extensionsAny')) { includeFromDifferentExtensionHosts = false; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 6b6fd263b04..1f0b894d033 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -92,6 +92,7 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; $unregisterCommand(id: string): void; + $fireCommandActivationEvent(id: string): void; $executeCommand(id: string, args: any[] | SerializableObjectWithBuffers, retry: boolean): Promise; $getCommands(): Promise; } @@ -1586,27 +1587,6 @@ export interface IWorkspaceEditEntryMetadataDto { iconPath?: { id: string } | UriComponents | { light: UriComponents; dark: UriComponents }; } -export const enum WorkspaceEditType { - File = 1, - Text = 2, - Cell = 3, -} - -export interface IWorkspaceFileEditDto { - _type: WorkspaceEditType.File; - oldUri?: UriComponents; - newUri?: UriComponents; - options?: languages.WorkspaceFileEditOptions; - metadata?: IWorkspaceEditEntryMetadataDto; -} - -export interface IWorkspaceTextEditDto { - _type: WorkspaceEditType.Text; - resource: UriComponents; - edit: languages.TextEdit & { insertAsSnippet?: boolean }; - modelVersionId?: number; - metadata?: IWorkspaceEditEntryMetadataDto; -} export type ICellEditOperationDto = notebookCommon.ICellPartialMetadataEdit @@ -1618,31 +1598,19 @@ export type ICellEditOperationDto = cells: NotebookCellDataDto[]; }; -export interface IWorkspaceCellEditDto { - _type: WorkspaceEditType.Cell; - resource: UriComponents; - notebookVersionId?: number; - metadata?: IWorkspaceEditEntryMetadataDto; - edit: ICellEditOperationDto; -} +export type IWorkspaceCellEditDto = Dto> & { cellEdit: ICellEditOperationDto }; + +export type IWorkspaceFileEditDto = Dto; + +export type IWorkspaceTextEditDto = Dto; export interface IWorkspaceEditDto { edits: Array; } -export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): languages.WorkspaceEdit { +export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): languages.WorkspaceEdit | undefined { if (data && data.edits) { - for (const edit of data.edits) { - if (typeof (edit).resource === 'object') { - (edit).resource = URI.revive((edit).resource); - } else { - (edit).newUri = URI.revive((edit).newUri); - (edit).oldUri = URI.revive((edit).oldUri); - } - if (edit.metadata && edit.metadata.iconPath) { - edit.metadata = revive(edit.metadata); - } - } + revive(data); } return data; } diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 7e6668d8a2a..62c9a3c5c9b 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -169,8 +169,11 @@ export class ExtHostCommands implements ExtHostCommandsShape { private async _doExecuteCommand(id: string, args: any[], retry: boolean): Promise { if (this._commands.has(id)) { - // we stay inside the extension host and support - // to pass any kind of parameters around + // - We stay inside the extension host and support + // to pass any kind of parameters around. + // - We still emit the corresponding activation event + // BUT we don't await that event + this.#proxy.$fireCommandActivationEvent(id); return this._executeContributedCommand(id, args, false); } else { @@ -206,7 +209,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { } catch (e) { // Rerun the command when it wasn't known, had arguments, and when retry // is enabled. We do this because the command might be registered inside - // the extension host now and can therfore accept the arguments as-is. + // the extension host now and can therefore accept the arguments as-is. if (e instanceof Error && e.message === '$executeCommand:retry') { return this._doExecuteCommand(id, args, false); } else { diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 05f7cb963d6..7a90e5db0f9 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { illegalState } from 'vs/base/common/errors'; -import { ExtHostDocumentSaveParticipantShape, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentSaveParticipantShape, IWorkspaceEditDto, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -146,12 +146,12 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic if (Array.isArray(value) && (value).every(e => e instanceof TextEdit)) { for (const { newText, newEol, range } of value) { dto.edits.push({ - _type: WorkspaceEditType.Text, resource: document.uri, - edit: { + versionId: undefined, + textEdit: { range: range && Range.from(range), text: newText, - eol: newEol && EndOfLine.from(newEol) + eol: newEol && EndOfLine.from(newEol), } }); } diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts index 1fbc2d487de..21a42adb1e4 100644 --- a/src/vs/workbench/api/common/extHostEditorTabs.ts +++ b/src/vs/workbench/api/common/extHostEditorTabs.ts @@ -195,7 +195,7 @@ class ExtHostEditorTabGroup { this._activeTabId = operation.tabDto.id; } else if (this._activeTabId === operation.tabDto.id && !operation.tabDto.isActive) { // Events aren't guaranteed to be in order so if we receive a dto that matches the active tab id - // but isn't active we mark the active tab id as empty. This prevent onDidActiveTabChange frorm + // but isn't active we mark the active tab id as empty. This prevent onDidActiveTabChange from // firing incorrectly this._activeTabId = ''; } @@ -256,12 +256,12 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs { return this._closeTabs(tabsOrTabGroups as vscode.Tab[], preserveFocus); } }, - // move: async (tab: vscode.Tab, viewColumn: ViewColumn, index: number, preservceFocus?: boolean) => { + // move: async (tab: vscode.Tab, viewColumn: ViewColumn, index: number, preserveFocus?: boolean) => { // const extHostTab = this._findExtHostTabFromApi(tab); // if (!extHostTab) { // throw new Error('Invalid tab'); // } - // this._proxy.$moveTab(extHostTab.tabId, index, typeConverters.ViewColumn.from(viewColumn), preservceFocus); + // this._proxy.$moveTab(extHostTab.tabId, index, typeConverters.ViewColumn.from(viewColumn), preserveFocus); // return; // } }; diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index d4f86a58255..79aa5cd22f4 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -16,6 +16,7 @@ import { FileOperation } from 'vs/platform/files/common/files'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; class FileSystemWatcher implements vscode.FileSystemWatcher { @@ -223,14 +224,14 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ private async _fireWillEvent(emitter: AsyncEmitter, data: IWaitUntilData, timeout: number, token: CancellationToken): Promise { const extensionNames = new Set(); - const edits: WorkspaceEdit[] = []; + const edits: [IExtensionDescription, WorkspaceEdit][] = []; - await emitter.fireAsync(data, token, async (thenable, listener) => { + await emitter.fireAsync(data, token, async (thenable: Promise, listener) => { // ignore all results except for WorkspaceEdits. Those are stored in an array. const now = Date.now(); const result = await Promise.resolve(thenable); if (result instanceof WorkspaceEdit) { - edits.push(result); + edits.push([(>listener).extension, result]); extensionNames.add((>listener).extension.displayName ?? (>listener).extension.identifier.value); } @@ -249,11 +250,11 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ // concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer const dto: IWorkspaceEditDto = { edits: [] }; - for (const edit of edits) { + for (const [extension, edit] of edits) { const { edits } = typeConverter.WorkspaceEdit.from(edit, { getTextDocumentVersion: uri => this._extHostDocumentsAndEditors.getDocument(uri)?.version, getNotebookDocumentVersion: () => undefined, - }); + }, isProposedApiEnabled(extension, 'snippetWorkspaceEdit')); dto.edits = dto.edits.concat(edits); } return { edit: dto, extensionNames: Array.from(extensionNames) }; diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts index b492b0c9ed4..b53d846c2e1 100644 --- a/src/vs/workbench/api/common/extHostInteractive.ts +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostInteractiveShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -15,7 +16,8 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { mainContext: IMainContext, private _extHostNotebooks: ExtHostNotebookController, private _textDocumentsAndEditors: ExtHostDocumentsAndEditors, - private _commands: ExtHostCommands + private _commands: ExtHostCommands, + _logService: ILogService ) { const openApiCommand = new ApiCommand( 'interactive.open', @@ -28,10 +30,13 @@ export class ExtHostInteractive implements ExtHostInteractiveShape { new ApiCommandArgument('title', 'Interactive editor title', v => true, v => v) ], new ApiCommandResult<{ notebookUri: UriComponents; inputUri: UriComponents; notebookEditorId?: string }, { notebookUri: URI; inputUri: URI; notebookEditor?: NotebookEditor }>('Notebook and input URI', (v: { notebookUri: UriComponents; inputUri: UriComponents; notebookEditorId?: string }) => { + _logService.debug('[ExtHostInteractive] open iw with notebook editor id', v.notebookEditorId); if (v.notebookEditorId !== undefined) { const editor = this._extHostNotebooks.getEditorById(v.notebookEditorId); + _logService.debug('[ExtHostInteractive] notebook editor found', editor.id); return { notebookUri: URI.revive(v.notebookUri), inputUri: URI.revive(v.inputUri), notebookEditor: editor.apiEditor }; } + _logService.debug('[ExtHostInteractive] notebook editor not found, uris for the interactive document', v.notebookUri, v.inputUri); return { notebookUri: URI.revive(v.notebookUri), inputUri: URI.revive(v.inputUri) }; }) ); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index afdb6fbb015..4c70e92c5e6 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -446,7 +446,7 @@ class CodeActionAdapter { title: candidate.title, command: candidate.command && this._commands.toInternal(candidate.command, disposables), diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from), - edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), + edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit')), kind: candidate.kind && candidate.kind.value, isPreferred: candidate.isPreferred, disabled: candidate.disabled?.reason @@ -467,7 +467,7 @@ class CodeActionAdapter { } const resolvedItem = (await this._provider.resolveCodeAction(item, token)) ?? item; return resolvedItem?.edit - ? typeConvert.WorkspaceEdit.from(resolvedItem.edit) + ? typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit')) : undefined; } @@ -522,7 +522,7 @@ class DocumentPasteEditProvider { return { insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, - additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined, + additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined, }; } } @@ -1808,7 +1808,7 @@ class DocumentOnDropEditAdapter { } return { insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, - additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined, + additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined, }; } } diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 356c29a7a76..6cc843c39dc 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -59,7 +59,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebookKernels); - // todo@rebornix @joyceerhl: move to APICommands once stablized. + // todo@rebornix @joyceerhl: move to APICommands once stabilized. const selectKernelApiCommand = new ApiCommand( 'notebook.selectKernel', '_notebook.selectKernel', diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index a7c81eb67d2..559d09d0aa0 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -774,7 +774,7 @@ export class MirroredTestCollection extends AbstractIncrementalTestCollection(); /** - * Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`. + * Change emitter that fires with the same semantics as `TestObserver.onDidChangeTests`. */ public readonly onDidChangeTests = this.changeEmitter.event; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9b733dc8951..52b5d2c712e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -589,47 +589,55 @@ export namespace WorkspaceEdit { if (entry._type === types.FileEditType.File) { // file operation - result.edits.push({ - _type: extHostProtocol.WorkspaceEditType.File, - oldUri: entry.from, - newUri: entry.to, + result.edits.push({ + oldResource: entry.from, + newResource: entry.to, options: entry.options, metadata: entry.metadata }); } else if (entry._type === types.FileEditType.Text) { - // text edits - const dto = { - _type: extHostProtocol.WorkspaceEditType.Text, + result.edits.push({ resource: entry.uri, - edit: TextEdit.from(entry.edit), - modelVersionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, + textEdit: TextEdit.from(entry.edit), + versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, metadata: entry.metadata - }; - if (allowSnippetTextEdit && entry.edit.newText2 instanceof types.SnippetString) { - dto.edit.insertAsSnippet = true; - dto.edit.text = entry.edit.newText2.value; + }); + } else if (entry._type === types.FileEditType.Snippet) { + // snippet text edits + if (!allowSnippetTextEdit) { + console.warn(`DROPPING snippet text edit because proposal IS NOT ENABLED`, entry); + continue; } - result.edits.push(dto); + result.edits.push({ + resource: entry.uri, + textEdit: { + range: Range.from(entry.range), + text: entry.edit.value, + insertAsSnippet: true + }, + versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined, + metadata: entry.metadata + }); } else if (entry._type === types.FileEditType.Cell) { - result.edits.push({ - _type: extHostProtocol.WorkspaceEditType.Cell, + // cell edit + result.edits.push({ metadata: entry.metadata, resource: entry.uri, - edit: entry.edit, + cellEdit: entry.edit, notebookMetadata: entry.notebookMetadata, notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri) }); } else if (entry._type === types.FileEditType.CellReplace) { - result.edits.push({ - _type: extHostProtocol.WorkspaceEditType.Cell, + // cell replace + result.edits.push({ metadata: entry.metadata, resource: entry.uri, notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri), - edit: { + cellEdit: { editType: notebooks.CellEditType.Replace, index: entry.index, count: entry.count, @@ -645,16 +653,16 @@ export namespace WorkspaceEdit { export function to(value: extHostProtocol.IWorkspaceEditDto) { const result = new types.WorkspaceEdit(); for (const edit of value.edits) { - if ((edit).edit) { + if ((edit).textEdit) { result.replace( URI.revive((edit).resource), - Range.to((edit).edit.range), - (edit).edit.text + Range.to((edit).textEdit.range), + (edit).textEdit.text ); } else { result.renameFile( - URI.revive((edit).oldUri!), - URI.revive((edit).newUri!), + URI.revive((edit).oldResource!), + URI.revive((edit).newResource!), (edit).options ); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index aac7633b6f0..2cbff63ec7d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -19,16 +19,6 @@ import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol'; import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; -function es5ClassCompat(target: Function): any { - ///@ts-expect-error - function _() { return Reflect.construct(target, arguments, this.constructor); } - Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!); - Object.setPrototypeOf(_, target); - Object.setPrototypeOf(_.prototype, target.prototype); - return _; -} - -@es5ClassCompat export class Disposable { static from(...inDisposables: { dispose(): any }[]): Disposable { @@ -59,7 +49,6 @@ export class Disposable { } } -@es5ClassCompat export class Position { static Min(...positions: Position[]): Position { @@ -240,7 +229,6 @@ export class Position { } } -@es5ClassCompat export class Range { static isRange(thing: any): thing is vscode.Range { @@ -386,7 +374,6 @@ export class Range { } } -@es5ClassCompat export class Selection extends Range { static isSelection(thing: any): thing is Selection { @@ -515,7 +502,6 @@ export enum EnvironmentVariableMutatorType { Prepend = 3 } -@es5ClassCompat export class TextEdit { static isTextEdit(thing: any): thing is TextEdit { @@ -549,7 +535,6 @@ export class TextEdit { protected _range: Range; protected _newText: string | null; - newText2?: string | SnippetString; protected _newEol?: EndOfLine; get range(): Range { @@ -599,7 +584,6 @@ export class TextEdit { } } -@es5ClassCompat export class NotebookEdit implements vscode.NotebookEdit { static isNotebookCellEdit(thing: any): thing is NotebookEdit { @@ -660,6 +644,7 @@ export const enum FileEditType { Text = 2, Cell = 3, CellReplace = 5, + Snippet = 6, } export interface IFileOperation { @@ -677,6 +662,14 @@ export interface IFileTextEdit { metadata?: vscode.WorkspaceEditEntryMetadata; } +export interface IFileSnippetTextEdit { + _type: FileEditType.Snippet; + uri: URI; + range: vscode.Range; + edit: vscode.SnippetString; + metadata?: vscode.WorkspaceEditEntryMetadata; +} + export interface IFileCellEdit { _type: FileEditType.Cell; uri: URI; @@ -695,9 +688,8 @@ export interface ICellEdit { } -type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileCellEdit | ICellEdit; +type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileSnippetTextEdit | IFileCellEdit | ICellEdit; -@es5ClassCompat export class WorkspaceEdit implements vscode.WorkspaceEdit { private readonly _edits: WorkspaceEditEntry[] = []; @@ -762,8 +754,12 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { // --- text - replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { - this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata }); + replace(uri: URI, range: Range, newText: string | vscode.SnippetString, metadata?: vscode.WorkspaceEditEntryMetadata): void { + if (typeof newText === 'string') { + this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata }); + } else { + this._edits.push({ _type: FileEditType.Snippet, uri, range, edit: newText, metadata }); + } } insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -844,7 +840,6 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } } -@es5ClassCompat export class SnippetString { static isSnippetString(thing: any): thing is SnippetString { @@ -951,7 +946,6 @@ export enum DiagnosticSeverity { Error = 0 } -@es5ClassCompat export class Location { static isLocation(thing: any): thing is vscode.Location { @@ -990,7 +984,6 @@ export class Location { } } -@es5ClassCompat export class DiagnosticRelatedInformation { static is(thing: any): thing is DiagnosticRelatedInformation { @@ -1024,7 +1017,6 @@ export class DiagnosticRelatedInformation { } } -@es5ClassCompat export class Diagnostic { range: Range; @@ -1075,7 +1067,6 @@ export class Diagnostic { } } -@es5ClassCompat export class Hover { public contents: (vscode.MarkdownString | vscode.MarkedString)[]; @@ -1103,7 +1094,6 @@ export enum DocumentHighlightKind { Write = 2 } -@es5ClassCompat export class DocumentHighlight { range: Range; @@ -1155,7 +1145,6 @@ export enum SymbolTag { Deprecated = 1, } -@es5ClassCompat export class SymbolInformation { static validate(candidate: SymbolInformation): void { @@ -1200,7 +1189,6 @@ export class SymbolInformation { } } -@es5ClassCompat export class DocumentSymbol { static validate(candidate: DocumentSymbol): void { @@ -1239,7 +1227,6 @@ export enum CodeActionTriggerKind { Automatic = 2, } -@es5ClassCompat export class CodeAction { title: string; @@ -1260,7 +1247,6 @@ export class CodeAction { } -@es5ClassCompat export class CodeActionKind { private static readonly sep = '.'; @@ -1300,7 +1286,6 @@ CodeActionKind.Source = CodeActionKind.Empty.append('source'); CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); CodeActionKind.SourceFixAll = CodeActionKind.Source.append('fixAll'); -@es5ClassCompat export class SelectionRange { range: Range; @@ -1367,7 +1352,6 @@ export enum LanguageStatusSeverity { } -@es5ClassCompat export class CodeLens { range: Range; @@ -1384,7 +1368,6 @@ export class CodeLens { } } -@es5ClassCompat export class MarkdownString implements vscode.MarkdownString { readonly #delegate: BaseMarkdownString; @@ -1455,7 +1438,6 @@ export class MarkdownString implements vscode.MarkdownString { } } -@es5ClassCompat export class ParameterInformation { label: string | [number, number]; @@ -1467,7 +1449,6 @@ export class ParameterInformation { } } -@es5ClassCompat export class SignatureInformation { label: string; @@ -1482,7 +1463,6 @@ export class SignatureInformation { } } -@es5ClassCompat export class SignatureHelp { signatures: SignatureInformation[]; @@ -1506,7 +1486,6 @@ export enum InlayHintKind { Parameter = 2, } -@es5ClassCompat export class InlayHintLabelPart { value: string; @@ -1519,7 +1498,6 @@ export class InlayHintLabelPart { } } -@es5ClassCompat export class InlayHint implements vscode.InlayHint { label: string | InlayHintLabelPart[]; @@ -1588,7 +1566,6 @@ export interface CompletionItemLabel { description?: string; } -@es5ClassCompat export class CompletionItem implements vscode.CompletionItem { label: string | CompletionItemLabel; @@ -1627,7 +1604,6 @@ export class CompletionItem implements vscode.CompletionItem { } } -@es5ClassCompat export class CompletionList { isIncomplete?: boolean; @@ -1639,7 +1615,6 @@ export class CompletionList { } } -@es5ClassCompat export class InlineSuggestion implements vscode.InlineCompletionItem { filterText?: string; @@ -1654,7 +1629,6 @@ export class InlineSuggestion implements vscode.InlineCompletionItem { } } -@es5ClassCompat export class InlineSuggestionList implements vscode.InlineCompletionList { items: vscode.InlineCompletionItemNew[]; @@ -1665,7 +1639,6 @@ export class InlineSuggestionList implements vscode.InlineCompletionList { } } -@es5ClassCompat export class InlineSuggestionNew implements vscode.InlineCompletionItemNew { insertText: string; range?: Range; @@ -1678,7 +1651,6 @@ export class InlineSuggestionNew implements vscode.InlineCompletionItemNew { } } -@es5ClassCompat export class InlineSuggestionsNew implements vscode.InlineCompletionListNew { items: vscode.InlineCompletionItemNew[]; @@ -1772,7 +1744,6 @@ export namespace TextEditorSelectionChangeKind { } } -@es5ClassCompat export class DocumentLink { range: Range; @@ -1793,7 +1764,6 @@ export class DocumentLink { } } -@es5ClassCompat export class Color { readonly red: number; readonly green: number; @@ -1810,7 +1780,6 @@ export class Color { export type IColorFormat = string | { opaque: string; transparent: string }; -@es5ClassCompat export class ColorInformation { range: Range; @@ -1828,7 +1797,6 @@ export class ColorInformation { } } -@es5ClassCompat export class ColorPresentation { label: string; textEdit?: TextEdit; @@ -1911,7 +1879,6 @@ export enum TaskPanelKind { New = 3 } -@es5ClassCompat export class TaskGroup implements vscode.TaskGroup { isDefault: boolean | undefined; @@ -1963,7 +1930,6 @@ function computeTaskExecutionId(values: string[]): string { return id; } -@es5ClassCompat export class ProcessExecution implements vscode.ProcessExecution { private _process: string; @@ -2034,7 +2000,6 @@ export class ProcessExecution implements vscode.ProcessExecution { } } -@es5ClassCompat export class ShellExecution implements vscode.ShellExecution { private _commandLine: string | undefined; @@ -2149,7 +2114,6 @@ export class CustomExecution implements vscode.CustomExecution { } } -@es5ClassCompat export class Task implements vscode.Task { private static ExtensionCallbackType: string = 'customExecution'; @@ -2406,7 +2370,6 @@ export enum ProgressLocation { Notification = 15 } -@es5ClassCompat export class TreeItem { label?: string | vscode.TreeItemLabel; @@ -2434,7 +2397,6 @@ export enum TreeItemCollapsibleState { Expanded = 2 } -@es5ClassCompat export class DataTransferItem { async asString(): Promise { @@ -2448,7 +2410,6 @@ export class DataTransferItem { constructor(public readonly value: any) { } } -@es5ClassCompat export class DataTransfer implements vscode.DataTransfer { #items = new Map(); @@ -2490,7 +2451,6 @@ export class DataTransfer implements vscode.DataTransfer { } } -@es5ClassCompat export class DocumentDropEdit { insertText: string | SnippetString; @@ -2501,7 +2461,6 @@ export class DocumentDropEdit { } } -@es5ClassCompat export class DocumentPasteEdit { insertText: string | SnippetString; @@ -2512,7 +2471,6 @@ export class DocumentPasteEdit { } } -@es5ClassCompat export class ThemeIcon { static File: ThemeIcon; @@ -2530,7 +2488,6 @@ ThemeIcon.File = new ThemeIcon('file'); ThemeIcon.Folder = new ThemeIcon('folder'); -@es5ClassCompat export class ThemeColor { id: string; constructor(id: string) { @@ -2546,7 +2503,6 @@ export enum ConfigurationTarget { WorkspaceFolder = 3 } -@es5ClassCompat export class RelativePattern implements IRelativePattern { pattern: string; @@ -2600,7 +2556,6 @@ export class RelativePattern implements IRelativePattern { } } -@es5ClassCompat export class Breakpoint { private _id: string | undefined; @@ -2631,7 +2586,6 @@ export class Breakpoint { } } -@es5ClassCompat export class SourceBreakpoint extends Breakpoint { readonly location: Location; @@ -2644,7 +2598,6 @@ export class SourceBreakpoint extends Breakpoint { } } -@es5ClassCompat export class FunctionBreakpoint extends Breakpoint { readonly functionName: string; @@ -2654,7 +2607,6 @@ export class FunctionBreakpoint extends Breakpoint { } } -@es5ClassCompat export class DataBreakpoint extends Breakpoint { readonly label: string; readonly dataId: string; @@ -2672,7 +2624,6 @@ export class DataBreakpoint extends Breakpoint { } -@es5ClassCompat export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { readonly command: string; readonly args: string[]; @@ -2685,7 +2636,6 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { } } -@es5ClassCompat export class DebugAdapterServer implements vscode.DebugAdapterServer { readonly port: number; readonly host?: string; @@ -2696,13 +2646,11 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } } -@es5ClassCompat export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer { constructor(public readonly path: string) { } } -@es5ClassCompat export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation { readonly implementation: vscode.DebugAdapter; @@ -2711,7 +2659,6 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli } } -@es5ClassCompat export class EvaluatableExpression implements vscode.EvaluatableExpression { readonly range: vscode.Range; readonly expression?: string; @@ -2732,7 +2679,6 @@ export enum InlineCompletionTriggerKindNew { Automatic = 1, } -@es5ClassCompat export class InlineValueText implements vscode.InlineValueText { readonly range: Range; readonly text: string; @@ -2743,7 +2689,6 @@ export class InlineValueText implements vscode.InlineValueText { } } -@es5ClassCompat export class InlineValueVariableLookup implements vscode.InlineValueVariableLookup { readonly range: Range; readonly variableName?: string; @@ -2756,7 +2701,6 @@ export class InlineValueVariableLookup implements vscode.InlineValueVariableLook } } -@es5ClassCompat export class InlineValueEvaluatableExpression implements vscode.InlineValueEvaluatableExpression { readonly range: Range; readonly expression?: string; @@ -2767,7 +2711,6 @@ export class InlineValueEvaluatableExpression implements vscode.InlineValueEvalu } } -@es5ClassCompat export class InlineValueContext implements vscode.InlineValueContext { readonly frameId: number; @@ -2787,7 +2730,6 @@ export enum FileChangeType { Deleted = 3, } -@es5ClassCompat export class FileSystemError extends Error { static FileExists(messageOrUri?: string | URI): FileSystemError { @@ -2837,7 +2779,6 @@ export class FileSystemError extends Error { //#region folding api -@es5ClassCompat export class FoldingRange { start: number; @@ -3127,7 +3068,6 @@ export enum DebugConsoleMode { //#endregion -@es5ClassCompat export class QuickInputButtons { static readonly Back: vscode.QuickInputButton = { iconPath: new ThemeIcon('arrow-left') }; @@ -3183,7 +3123,6 @@ export class FileDecoration { //#region Theming -@es5ClassCompat export class ColorTheme implements vscode.ColorTheme { constructor(public readonly kind: ColorThemeKind) { } @@ -3478,7 +3417,6 @@ export class NotebookRendererScript { //#region Timeline -@es5ClassCompat export class TimelineItem implements vscode.TimelineItem { constructor(public label: string, public timestamp: number) { } } @@ -3568,7 +3506,6 @@ export enum TestRunProfileKind { Coverage = 3, } -@es5ClassCompat export class TestRunRequest implements vscode.TestRunRequest { constructor( public readonly include: vscode.TestItem[] | undefined = undefined, @@ -3577,7 +3514,6 @@ export class TestRunRequest implements vscode.TestRunRequest { ) { } } -@es5ClassCompat export class TestMessage implements vscode.TestMessage { public expectedOutput?: string; public actualOutput?: string; @@ -3593,7 +3529,6 @@ export class TestMessage implements vscode.TestMessage { constructor(public message: string | vscode.MarkdownString) { } } -@es5ClassCompat export class TestTag implements vscode.TestTag { constructor(public readonly id: string) { } } @@ -3601,12 +3536,10 @@ export class TestTag implements vscode.TestTag { //#endregion //#region Test Coverage -@es5ClassCompat export class CoveredCount implements vscode.CoveredCount { constructor(public covered: number, public total: number) { } } -@es5ClassCompat export class FileCoverage implements vscode.FileCoverage { public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage { const statements = new CoveredCount(0, 0); @@ -3650,7 +3583,6 @@ export class FileCoverage implements vscode.FileCoverage { ) { } } -@es5ClassCompat export class StatementCoverage implements vscode.StatementCoverage { constructor( public executionCount: number, @@ -3659,7 +3591,6 @@ export class StatementCoverage implements vscode.StatementCoverage { ) { } } -@es5ClassCompat export class BranchCoverage implements vscode.BranchCoverage { constructor( public executionCount: number, @@ -3667,7 +3598,6 @@ export class BranchCoverage implements vscode.BranchCoverage { ) { } } -@es5ClassCompat export class FunctionCoverage implements vscode.FunctionCoverage { constructor( public executionCount: number, diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 9d805271e48..9127c0db90e 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -228,7 +228,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac if (this._actualWorkspace) { if (this._actualWorkspace.configuration) { if (this._actualWorkspace.isUntitled) { - return URI.from({ scheme: Schemas.untitled, path: basename(dirname(this._actualWorkspace.configuration)) }); // Untitled Worspace: return untitled URI + return URI.from({ scheme: Schemas.untitled, path: basename(dirname(this._actualWorkspace.configuration)) }); // Untitled Workspace: return untitled URI } return this._actualWorkspace.configuration; // Workspace: return the configuration location diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index f7f206f0649..6f0445ab393 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -78,6 +78,7 @@ export interface ITaskSourceDTO { scope?: number | UriComponents; color?: string; icon?: string; + hide?: boolean; } export interface ITaskHandleDTO { diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index b3fd49e72b2..2ceac7fde44 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -100,7 +100,9 @@ suite('ExtHostLanguageFeatureCommands', function () { override async activateByEvent() { } - + override activationEventIsDone(activationEvent: string): boolean { + return true; + } }); services.set(ICommandService, new SyncDescriptor(class extends mock() { diff --git a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts index 3b393b35347..906405cd6b7 100644 --- a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts +++ b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MainContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, IWorkspaceEditDto, MainThreadBulkEditsShape, IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { NullLogService } from 'vs/platform/log/common/log'; -import { assertType } from 'vs/base/common/types'; import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; @@ -50,8 +49,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription); assert.strictEqual(workspaceResourceEdits.edits.length, 1); const [first] = workspaceResourceEdits.edits; - assertType(first._type === WorkspaceEditType.Text); - assert.strictEqual(first.modelVersionId, 1337); + assert.strictEqual((first).versionId, 1337); }); test('does not use version id if document is not available', async () => { @@ -60,8 +58,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription); assert.strictEqual(workspaceResourceEdits.edits.length, 1); const [first] = workspaceResourceEdits.edits; - assertType(first._type === WorkspaceEditType.Text); - assert.ok(typeof first.modelVersionId === 'undefined'); + assert.ok(typeof (first).versionId === 'undefined'); }); }); diff --git a/src/vs/workbench/api/test/browser/extHostCommands.test.ts b/src/vs/workbench/api/test/browser/extHostCommands.test.ts index da9b47ccda9..e38f919bee8 100644 --- a/src/vs/workbench/api/test/browser/extHostCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostCommands.test.ts @@ -90,4 +90,28 @@ suite('ExtHostCommands', function () { assert.strictEqual(result, 17); assert.strictEqual(count, 2); }); + + test('onCommand:abc activates extensions when executed from command palette, but not when executed programmatically with vscode.commands.executeCommand #150293', async function () { + + const activationEvents: string[] = []; + + const shape = new class extends mock() { + override $registerCommand(id: string): void { + // + } + override $fireCommandActivationEvent(id: string): void { + activationEvents.push(id); + } + }; + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); + + commands.registerCommand(true, 'extCmd', (args: any): any => args); + + const result = await commands.executeCommand('extCmd', this); + assert.strictEqual(result, this); + assert.deepStrictEqual(activationEvents, ['extCmd']); + }); }); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts index 70e338176d7..c50aabb812f 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts @@ -266,8 +266,8 @@ suite('ExtHostDocumentSaveParticipant', () => { sub.dispose(); assert.strictEqual(dto.edits.length, 2); - assert.ok((dto.edits[0]).edit); - assert.ok((dto.edits[1]).edit); + assert.ok((dto.edits[0]).textEdit); + assert.ok((dto.edits[1]).textEdit); }); }); @@ -317,7 +317,7 @@ suite('ExtHostDocumentSaveParticipant', () => { for (const edit of dto.edits) { const uri = URI.revive((edit).resource); - const { text, range } = (edit).edit; + const { text, range } = (edit).textEdit; documents.$acceptModelChanged(uri, { changes: [{ range, diff --git a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts index 052719d3f41..e158253f4dc 100644 --- a/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostMessagerService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { mock } from 'vs/base/test/common/mock'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; @@ -24,8 +24,10 @@ const emptyCommandService: ICommandService = { const emptyNotificationService = new class implements INotificationService { declare readonly _serviceBrand: undefined; + doNotDisturbMode: boolean = false; onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; + onDidChangeDoNotDisturbMode: Event = Event.None; notify(...args: any[]): never { throw new Error('not implemented'); } @@ -44,19 +46,17 @@ const emptyNotificationService = new class implements INotificationService { status(message: string | Error, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setFilter(filter: NotificationsFilter): void { - throw new Error('not implemented.'); - } }; class EmptyNotificationService implements INotificationService { declare readonly _serviceBrand: undefined; - + doNotDisturbMode: boolean = false; constructor(private withNotify: (notification: INotification) => void) { } onDidAddNotification: Event = Event.None; onDidRemoveNotification: Event = Event.None; + onDidChangeDoNotDisturbMode: Event = Event.None; notify(notification: INotification): INotificationHandle { this.withNotify(notification); @@ -77,9 +77,6 @@ class EmptyNotificationService implements INotificationService { status(message: string, options?: IStatusMessageOptions): IDisposable { return Disposable.None; } - setFilter(filter: NotificationsFilter): void { - throw new Error('Method not implemented.'); - } } suite('ExtHostMessageService', function () { diff --git a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts index a7a0b914279..650c28eb667 100644 --- a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts @@ -8,7 +8,6 @@ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { MarkdownString, NotebookCellOutputItem, NotebookData, LanguageSelector } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; -import { forEach } from 'vs/base/common/collections'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; @@ -74,13 +73,13 @@ suite('ExtHostTypeConverter', function () { const data = MarkdownString.from('*hello* [click](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2Ffoo%2Fbaz.ex%22%2C%22path%22%3A%22%2Fc%3A%2Ffoo%2Fbaz.ex%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22dev%22%7D)'); // assert that both uri get extracted but that the latter is only decoded once... assert.strictEqual(size(data.uris!), 2); - forEach(data.uris!, entry => { - if (entry.value.scheme === 'file') { - assert.ok(URI.revive(entry.value).toString().indexOf('file:///c%3A') === 0); + for (const value of Object.values(data.uris!)) { + if (value.scheme === 'file') { + assert.ok(URI.revive(value).toString().indexOf('file:///c%3A') === 0); } else { - assert.strictEqual(entry.value.scheme, 'command'); + assert.strictEqual(value.scheme, 'command'); } - }); + } }); test('Notebook metadata is ignored when using Notebook Serializer #125716', function () { diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index dac02ac748e..859c9c28ee8 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -9,7 +9,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ModelService } from 'vs/editor/common/services/modelService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWorkspaceTextEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { mock } from 'vs/base/test/common/mock'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; @@ -197,10 +197,9 @@ suite('MainThreadEditors', () => { const model = modelService.createModel('something', null, resource); const workspaceResourceEdit: IWorkspaceTextEditDto = { - _type: WorkspaceEditType.Text, resource: resource, - modelVersionId: model.getVersionId(), - edit: { + versionId: model.getVersionId(), + textEdit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } @@ -219,19 +218,17 @@ suite('MainThreadEditors', () => { const model = modelService.createModel('something', null, resource); const workspaceResourceEdit1: IWorkspaceTextEditDto = { - _type: WorkspaceEditType.Text, resource: resource, - modelVersionId: model.getVersionId(), - edit: { + versionId: model.getVersionId(), + textEdit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } }; const workspaceResourceEdit2: IWorkspaceTextEditDto = { - _type: WorkspaceEditType.Text, resource: resource, - modelVersionId: model.getVersionId(), - edit: { + versionId: model.getVersionId(), + textEdit: { text: 'asdfg', range: new Range(1, 1, 1, 1) } @@ -251,9 +248,9 @@ suite('MainThreadEditors', () => { test(`applyWorkspaceEdit with only resource edit`, () => { return bulkEdits.$tryApplyWorkspaceEdit({ edits: [ - { _type: WorkspaceEditType.File, oldUri: resource, newUri: resource, options: undefined }, - { _type: WorkspaceEditType.File, oldUri: undefined, newUri: resource, options: undefined }, - { _type: WorkspaceEditType.File, oldUri: resource, newUri: undefined, options: undefined } + { oldResource: resource, newResource: resource, options: undefined }, + { oldResource: undefined, newResource: resource, options: undefined }, + { oldResource: resource, newResource: undefined, options: undefined } ] }).then((result) => { assert.strictEqual(result, true); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 4b4cc8b38f0..9bf977172a8 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -1182,7 +1182,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 { constructor() { super({ id: 'workbench.action.customizeLayout', - title: localize('customizeLayout', "Customize Layout..."), + title: { original: 'Customize Layout...', value: localize('customizeLayout', "Customize Layout...") }, f1: true, icon: configureLayoutIcon, menu: [ diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index d5e691faaf9..a619fe25a1f 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -134,7 +134,7 @@ export abstract class Composite extends Component implements IComposite { * The composite will be on-DOM if visible is set to true and off-DOM otherwise. * * Typically this operation should be fast though because setVisible might be called many times during a session. - * If there is a long running opertaion it is fine to have it running in the background asyncly and return before. + * If there is a long running operation it is fine to have it running in the background asyncly and return before. */ setVisible(visible: boolean): void { if (this.visible !== !!visible) { @@ -238,7 +238,7 @@ export abstract class Composite extends Component implements IComposite { } /** - * A composite descriptor is a leightweight descriptor of a composite in the workbench. + * A composite descriptor is a lightweight descriptor of a composite in the workbench. */ export abstract class CompositeDescriptor { diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index df9b2c329d5..3d287e57781 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; +import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -24,6 +24,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { Schemas } from 'vs/base/common/network'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { IProductService } from 'vs/platform/product/common/productService'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -76,6 +77,7 @@ export class WorkbenchContextKeysHandler extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, @IEditorService private readonly editorService: IEditorService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -105,6 +107,9 @@ export class WorkbenchContextKeysHandler extends Disposable { // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); + // Product Quality + ProductQualityContext.bindTo(this.contextKeyService).set(this.productService.quality || ''); + // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorReadonlyContext.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index f5061e30594..8bc290a1655 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -189,7 +189,7 @@ export class ResourcesDropHandler { await this.workspaceEditingService.addFolders(folderURIs); } - // Finaly, enter untitled workspace when dropping >1 folders + // Finally, enter untitled workspace when dropping >1 folders else { await this.workspaceEditingService.createAndEnterWorkspace(folderURIs); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0258b818920..240b52cc2b9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -33,7 +33,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; import { assertIsDefined, isNumber } from 'vs/base/common/types'; -import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; @@ -1087,6 +1087,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (!restoring) { zenModeExitInfo.transitionedToFullScreen = toggleFullScreen; zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; + zenModeExitInfo.handleNotificationsDoNotDisturbMode = !this.notificationService.doNotDisturbMode; zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART); zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART); @@ -1114,13 +1115,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.windowState.runtime.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } - if (config.silentNotifications) { - this.notificationService.setFilter(NotificationsFilter.ERROR); + if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = true; } this.windowState.runtime.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS)) { - const filter = this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; - this.notificationService.setFilter(filter); + const zenModeSilentNotifications = !!this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS); + if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = zenModeSilentNotifications; + } } })); @@ -1155,13 +1158,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.centerEditorLayout(false, true); } + if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) { + this.notificationService.doNotDisturbMode = false; + } + setLineNumbers(); this.focus(); - // Clear notifications filter - this.notificationService.setFilter(NotificationsFilter.OFF); - toggleFullScreen = zenModeExitInfo.transitionedToFullScreen && this.windowState.runtime.fullscreen; } diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index 0e4cac0dddd..70a4e637a26 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -46,6 +46,7 @@ export const LayoutStateKeys = { ZEN_MODE_EXIT_INFO: new RuntimeStateKey('zenMode.exitInfo', StorageScope.WORKSPACE, StorageTarget.USER, { transitionedToCenteredEditorLayout: false, transitionedToFullScreen: false, + handleNotificationsDoNotDisturbMode: false, wasVisible: { auxiliaryBar: false, panel: false, diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 9d642b89346..21230d84325 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -139,7 +139,7 @@ export abstract class PaneComposite extends Composite implements IPaneComposite /** - * A Pane Composite descriptor is a leightweight descriptor of a Pane Composite in the workbench. + * A Pane Composite descriptor is a lightweight descriptor of a Pane Composite in the workbench. */ export class PaneCompositeDescriptor extends CompositeDescriptor { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 18bc56414ed..399f58c4f6a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -579,6 +579,13 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { order: 1 }); +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: localize('miShare', "Share"), + submenu: MenuId.MenubarShare, + group: '45_share', + order: 1, +}); + // Layout menu MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '2_appearance', diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index af98c5ae2c8..4d29cc8f143 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -597,7 +597,11 @@ export class EditorDropTarget extends Themable { private registerListeners(): void { this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e))); this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave())); - this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => this.onDragEnter(e))); + this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => { + if (!this.overlay) { + this.onDragEnter(e); + } + })); [this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd()))); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index 3402b0d5987..b4b01e896e8 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Action, IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER, TOGGLE_DO_NOT_DISTURB_MODE } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { Codicon } from 'vs/base/common/codicons'; @@ -23,6 +23,7 @@ const hideIcon = registerIcon('notifications-hide', Codicon.chevronDown, localiz const expandIcon = registerIcon('notifications-expand', Codicon.chevronUp, localize('expandIcon', 'Icon for the expand action in notifications.')); const collapseIcon = registerIcon('notifications-collapse', Codicon.chevronDown, localize('collapseIcon', 'Icon for the collapse action in notifications.')); const configureIcon = registerIcon('notifications-configure', Codicon.gear, localize('configureIcon', 'Icon for the configure action in notifications.')); +const doNotDisturbIcon = registerIcon('notifications-do-not-disturb', Codicon.bellSlash, localize('doNotDisturbIcon', 'Icon for the mute all action in notifications.')); export class ClearNotificationAction extends Action { @@ -60,6 +61,24 @@ export class ClearAllNotificationsAction extends Action { } } +export class ToggleDoNotDisturbAction extends Action { + + static readonly ID = TOGGLE_DO_NOT_DISTURB_MODE; + static readonly LABEL = localize('toggleDoNotDisturbMode', "Toggle Do Not Disturb Mode"); + + constructor( + id: string, + label: string, + @ICommandService private readonly commandService: ICommandService + ) { + super(id, label, ThemeIcon.asClassName(doNotDisturbIcon)); + } + + override async run(): Promise { + this.commandService.executeCommand(TOGGLE_DO_NOT_DISTURB_MODE); + } +} + export class HideNotificationsCenterAction extends Action { static readonly ID = HIDE_NOTIFICATIONS_CENTER; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index c99553bf978..a230bb759e2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -19,11 +19,12 @@ import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { localize } from 'vs/nls'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions'; +import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner, ToggleDoNotDisturbAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IAction } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { NotificationsCenterVisibleContext } from 'vs/workbench/common/contextkeys'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -40,6 +41,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente private workbenchDimensions: Dimension | undefined; private readonly notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(this.contextKeyService); private clearAllAction: ClearAllNotificationsAction | undefined; + private toggleDoNotDisturbAction: ToggleDoNotDisturbAction | undefined; constructor( private readonly container: HTMLElement, @@ -49,7 +51,8 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, ) { super(themeService); @@ -61,6 +64,11 @@ export class NotificationsCenter extends Themable implements INotificationsCente private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.layoutService.onDidLayout(dimension => this.layout(Dimension.lift(dimension)))); + this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.onDidChangeDoNotDisturbMode())); + } + + private onDidChangeDoNotDisturbMode(): void { + this.hide(); // hide the notification center when do not disturb is toggled } get isVisible(): boolean { @@ -154,6 +162,9 @@ export class NotificationsCenter extends Themable implements INotificationsCente this.clearAllAction = this._register(this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL)); notificationsToolBar.push(this.clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.clearAllAction) }); + this.toggleDoNotDisturbAction = this._register(this.instantiationService.createInstance(ToggleDoNotDisturbAction, ToggleDoNotDisturbAction.ID, ToggleDoNotDisturbAction.LABEL)); + notificationsToolBar.push(this.toggleDoNotDisturbAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.toggleDoNotDisturbAction) }); + const hideAllAction = this._register(this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL)); notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) }); @@ -316,6 +327,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente } } + registerThemingParticipant((theme, collector) => { const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER); if (notificationBorderColor) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index ee0cd3f780c..7649aba7b08 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -14,6 +14,7 @@ import { IListService, WorkbenchList } from 'vs/platform/list/browser/listServic import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NotificationMetrics, NotificationMetricsClassification, notificationToMetrics } from 'vs/workbench/browser/parts/notifications/notificationsTelemetry'; import { NotificationFocusedContext, NotificationsCenterVisibleContext, NotificationsToastsVisibleContext } from 'vs/workbench/common/contextkeys'; +import { INotificationService } from 'vs/platform/notification/common/notification'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -34,6 +35,7 @@ export const EXPAND_NOTIFICATION = 'notification.expand'; const TOGGLE_NOTIFICATION = 'notification.toggle'; export const CLEAR_NOTIFICATION = 'notification.clear'; export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll'; +export const TOGGLE_DO_NOT_DISTURB_MODE = 'notifications.toggleDoNotDisturbMode'; export interface INotificationsCenterController { readonly isVisible: boolean; @@ -240,13 +242,21 @@ export function registerNotificationCommands(center: INotificationsCenterControl } }); - /// Clear All Notifications + // Clear All Notifications CommandsRegistry.registerCommand(CLEAR_ALL_NOTIFICATIONS, () => center.clearAll()); + // Toggle Do Not Disturb Mode + CommandsRegistry.registerCommand(TOGGLE_DO_NOT_DISTURB_MODE, accessor => { + const notificationService = accessor.get(INotificationService); + + notificationService.doNotDisturbMode = !notificationService.doNotDisturbMode; + }); + // Commands for Command Palette const category = { value: localize('notifications', "Notifications"), original: 'Notifications' }; MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: { value: localize('showNotifications', "Show Notifications"), original: 'Show Notifications' }, category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: { value: localize('hideNotifications', "Hide Notifications"), original: 'Hide Notifications' }, category }, when: NotificationsCenterVisibleContext }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: { value: localize('clearAllNotifications', "Clear All Notifications"), original: 'Clear All Notifications' }, category } }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_DO_NOT_DISTURB_MODE, title: { value: localize('toggleDoNotDisturbMode', "Toggle Do Not Disturb Mode"), original: 'Toggle Do Not Disturb Mode' }, category } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: { value: localize('focusNotificationToasts', "Focus Notification Toast"), original: 'Focus Notification Toast' }, category }, when: NotificationsToastsVisibleContext }); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index ec31db99758..d3da50394ad 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -8,6 +8,7 @@ import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatus import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { localize } from 'vs/nls'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class NotificationsStatus extends Disposable { @@ -21,7 +22,8 @@ export class NotificationsStatus extends Disposable { constructor( private readonly model: INotificationsModel, - @IStatusbarService private readonly statusbarService: IStatusbarService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @INotificationService private readonly notificationService: INotificationService ) { super(); @@ -37,6 +39,7 @@ export class NotificationsStatus extends Disposable { private registerListeners(): void { this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e))); this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e))); + this._register(this.notificationService.onDidChangeDoNotDisturbMode(() => this.updateNotificationsCenterStatusItem())); } private onDidChangeNotification(e: INotificationChangeEvent): void { @@ -69,8 +72,9 @@ export class NotificationsStatus extends Disposable { } } - // Show the bell with a dot if there are unread or in-progress notifications - const statusProperties: IStatusbarEntry = { + // Show the status bar entry depending on do not disturb setting + + let statusProperties: IStatusbarEntry = { name: localize('status.notifications', "Notifications"), text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-dot)' : '$(bell)'}`, ariaLabel: localize('status.notifications', "Notifications"), @@ -79,6 +83,16 @@ export class NotificationsStatus extends Disposable { showBeak: this.isNotificationsCenterVisible }; + if (this.notificationService.doNotDisturbMode) { + statusProperties = { + ...statusProperties, + name: localize('status.doNotDisturb', "Do Not Disturb"), + text: `${notificationsInProgress > 0 || this.newNotificationsCount > 0 ? '$(bell-slash-dot)' : '$(bell-slash)'}`, + ariaLabel: localize('status.doNotDisturb', "Do Not Disturb"), + tooltip: localize('status.doNotDisturbTooltip', "Do Not Disturb Mode is Enabled") + }; + } + if (!this.notificationsCenterStatusItem) { this.notificationsCenterStatusItem = this.statusbarService.addEntry( statusProperties, diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index ae801b2921a..2263ec0769e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -823,6 +823,8 @@ export abstract class BasePanelPart extends CompositePart impleme const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); state.push({ id: compositeItem.id, name: viewContainerModel.title, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); placeholders.push({ id: compositeItem.id, name: this.getCompositeActions(compositeItem.id).activityAction.label }); + } else { + state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts index 9d9c7e3e23a..6b4685f4901 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts @@ -12,7 +12,7 @@ import { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statu import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; import { isThemeColor } from 'vs/editor/common/editorCommon'; -import { addDisposableListener, EventType, hide, show, append } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { assertIsDefined } from 'vs/base/common/types'; import { Command } from 'vs/editor/common/languages'; @@ -129,6 +129,8 @@ export class StatusbarEntryItem extends Disposable { this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { + EventHelper.stop(e); + this.executeCommand(command); } }); diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index de2f74a7b2f..cc6278c3a34 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -4,21 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { reset } from 'vs/base/browser/dom'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { Action, IAction } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { createActionViewItem, createAndFillInContextMenuActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { Action2, IMenuService, MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -57,7 +56,7 @@ export class CommandCenterControl { override render(container: HTMLElement): void { super.render(container); - container.classList.add('quickopen'); + container.classList.add('quickopen', 'left'); assertType(this.label); this.label.classList.add('search'); @@ -68,7 +67,7 @@ export class CommandCenterControl { this.workspaceTitle.classList.add('search-label'); this._updateFromWindowTitle(); reset(this.label, searchIcon, this.workspaceTitle); - this._renderAllQuickPickItem(container); + // this._renderAllQuickPickItem(container); this._store.add(windowTitle.onDidChange(this._updateFromWindowTitle, this)); } @@ -96,24 +95,23 @@ export class CommandCenterControl { : localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value); this._applyUpdateTooltip(title); } - - private _renderAllQuickPickItem(parent: HTMLElement): void { - const container = document.createElement('span'); - container.classList.add('all-options'); - parent.appendChild(container); - const action = new Action('all', localize('all', "Show Search Modes..."), Codicon.chevronDown.classNames, true, () => { - quickInputService.quickAccess.show('?'); - }); - const dropdown = new ActionViewItem(undefined, action, { icon: true, label: false, hoverDelegate }); - dropdown.render(container); - this._store.add(dropdown); - this._store.add(action); - } } return instantiationService.createInstance(InputLikeViewItem, action, { hoverDelegate }); - } - return createActionViewItem(instantiationService, action, { hoverDelegate }); + } else if (action instanceof MenuItemAction && action.id === 'commandCenter.help') { + + class ExtraClass extends MenuEntryActionViewItem { + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('quickopen', 'right'); + } + } + + return instantiationService.createInstance(ExtraClass, action, { hoverDelegate }); + + } else { + return createActionViewItem(instantiationService, action, { hoverDelegate }); + } } }); const menu = this._disposables.add(menuService.createMenu(MenuId.CommandCenter, contextKeyService)); @@ -143,6 +141,21 @@ export class CommandCenterControl { } } +registerAction2(class extends Action2 { + + constructor() { + super({ + id: 'commandCenter.help', + title: localize('all', "Show Search Modes..."), + icon: Codicon.chevronDown, + menu: { id: MenuId.CommandCenter, order: 100 } + }); + } + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show('?'); + } +}); + // --- theme colors // foreground (inactive and active) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index b661db98d5c..29f1142364e 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -101,13 +101,6 @@ color: var(--vscode-commandCenter-foreground); background-color: var(--vscode-commandCenter-background); border: 1px solid var(--vscode-commandCenter-border); - border-radius: 5px; - height: 20px; - line-height: 18px; - width: 38vw; - max-width: 600px; - min-width: 32px; - margin: 0 4px; flex-direction: row; justify-content: center; overflow: hidden; @@ -116,47 +109,55 @@ .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen:HOVER { color: var(--vscode-commandCenter-activeForeground); background-color: var(--vscode-commandCenter-activeBackground); - line-height: 18px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .quickopen .action-label { - background-color: transparent !important; - outline-color: transparent !important; +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left { + /* border,margin tricks */ + margin-left: 6px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-right: none; + + /* width */ + width: 38vw; + max-width: 600px; + min-width: 32px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.right { + /* border,margin tricks */ + margin-right: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border-left: none; + + /* width */ + width: 16px; +} + +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label { + height: 22px; + line-height: 22px; + padding: 0; + background-color: transparent; display: inline-flex; text-align: center; font-size: 12px; - background-color: inherit; justify-content: center; - width: calc(100% - 19px); + width: 100%; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-icon { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-icon { font-size: 14px; opacity: .8; margin: auto 3px; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-label { +.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-label { overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.all-options>.action-label { - text-align: center; - font-size: 12px; - width: 16px; - border-left: 1px solid transparent; - border-radius: 0; - padding-right: 0; -} - -.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .action-item.quickopen>.all-options>.action-label { - border-color: var(--vscode-commandCenter-border); -} - /* Menubar */ .monaco-workbench .part.titlebar>.titlebar-container>.menubar { /* move menubar above drag region as negative z-index on drag region cause greyscale AA */ diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 5f5e16ead26..9e9922b840a 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -27,7 +27,6 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua export class WindowTitle extends Disposable { - private static readonly NLS_UNSUPPORTED = localize('patchedWindowTitle', "[Unsupported]"); private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]"); private static readonly NLS_EXTENSION_HOST = localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); private static readonly TITLE_DIRTY = '\u25cf '; @@ -137,11 +136,6 @@ export class WindowTitle extends Disposable { if (this.properties.isAdmin) { suffix = WindowTitle.NLS_USER_IS_ADMIN; } - if (!this.properties.isPure) { - suffix = !suffix - ? WindowTitle.NLS_UNSUPPORTED - : `${suffix} ${WindowTitle.NLS_UNSUPPORTED}`; - } return { prefix, suffix }; } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 64889868f10..67efd200c19 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1495,11 +1495,11 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } const originalDataTransfer = toVSDataTransfer(originalEvent.dataTransfer); - addExternalEditorsDropData(originalDataTransfer, originalEvent); + addExternalEditorsDropData(originalDataTransfer, originalEvent, true); const outDataTransfer = new VSDataTransfer(); for (const [type, item] of originalDataTransfer.entries()) { - if (type === this.treeMimeType || dndController.dropMimeTypes.indexOf(type) >= 0) { + if (type === this.treeMimeType || dndController.dropMimeTypes.includes(type) || (item.asFile() && dndController.dropMimeTypes.includes(DataTransfers.FILES.toLowerCase()))) { outDataTransfer.append(type, item); if (type === this.treeMimeType) { try { diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 99c7b044635..637b1af74cd 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -251,7 +251,7 @@ export interface IWorkbenchConstructionOptions { readonly commands?: readonly ICommand[]; /** - * Optional default layout to apply on first time the workspace is opened (uness `force` is specified). + * Optional default layout to apply on first time the workspace is opened (unless `force` is specified). */ readonly defaultLayout?: IDefaultLayout; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index e83eb19ce63..e9b7f4fc8b0 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -263,7 +263,7 @@ export class BrowserMain extends Disposable { const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); // URI Identity diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 017f41f481a..5d3a8a9d037 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -96,7 +96,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['text', 'hidden'], 'default': 'text', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled text hint should be visible in the editor.") }, 'workbench.editor.languageDetection': { type: 'boolean', @@ -245,7 +245,7 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'workbench.editor.restoreViewState': { 'type': 'boolean', - 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the `#workbench.editor.sharedViewState#` setting to use the last known view state across all editor groups in case no previous view state was found for a editor group."), + 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the {0} setting to use the last known view state across all editor groups in case no previous view state was found for a editor group.", '`#workbench.editor.sharedViewState#`'), 'default': true, 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, @@ -278,7 +278,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'number', 'default': 10, 'exclusiveMinimum': 0, - 'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `#workbench.editor.limit.perEditorGroup#` setting to control this limit per editor group or across all groups.") + 'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the {0} setting to control this limit per editor group or across all groups.", '`#workbench.editor.limit.perEditorGroup#`') }, 'workbench.editor.limit.excludeDirty': { 'type': 'boolean', @@ -540,12 +540,12 @@ const registry = Registry.as(ConfigurationExtensions.Con 'window.titleSeparator': { 'type': 'string', 'default': isMacintosh ? ' \u2014 ' : ' - ', - 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.") + 'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`') }, 'window.commandCenter': { type: 'boolean', default: false, - markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.") + markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when {0} is set to {1}.", '`#window.titleBarStyle#`', '`custom`') }, 'window.menuBarVisibility': { 'type': 'string', @@ -557,7 +557,7 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('window.menuBarVisibility.toggle.mac', "Menu is hidden but can be displayed at the top of the window by executing the `Focus Application Menu` command.") : localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed at the top of the window via the Alt key."), localize('window.menuBarVisibility.hidden', "Menu is always hidden."), - localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when `#window.titleBarStyle#` is `native`.") + localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when {0} is {1}.", '`#window.titleBarStyle#`', '`native`') ], 'default': isWeb ? 'compact' : 'classic', 'scope': ConfigurationScope.APPLICATION, @@ -677,7 +677,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'zenMode.silentNotifications': { 'type': 'boolean', 'default': true, - 'description': localize('zenMode.silentNotifications', "Controls whether notifications are shown while in zen mode. If true, only error notifications will pop out.") + 'description': localize('zenMode.silentNotifications', "Controls whether notifications do not disturb mode should be enabled while in zen mode. If true, only error notifications will pop out.") } } }); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 25b50228d2a..d505032b79a 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -188,11 +188,8 @@ export class Workbench extends Layout { // // NOTE: Please do NOT register services here. Use `registerSingleton()` // from `workbench.common.main.ts` if the service is shared between - // native and web or `workbench.sandbox.main.ts` if the service - // is native only. - // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox + // desktop and web or `workbench.desktop.main.ts` if the service + // is desktop only. // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -408,7 +405,7 @@ export class Workbench extends Layout { } // Transition into restored phase after layout has restored - // but do not wait indefinitly on this to account for slow + // but do not wait indefinitely on this to account for slow // editors restoring. Since the workbench is fully functional // even when the visible editors have not resolved, we still // want contributions on the `Restored` phase to work before diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 9682738ae6c..907cfa0dda2 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -142,7 +142,7 @@ export class ResourceContextKey { // UNDEFINED! IT IS IMPORTANT THAT DEFAULTS ARE INHERITED // FROM THE PARENT CONTEXT AND ONLY UNDEFINED DOES THIS - static readonly Scheme = new RawContextKey('resourceScheme', undefined, { type: 'string', description: localize('resourceScheme', "The scheme of the rsource") }); + static readonly Scheme = new RawContextKey('resourceScheme', undefined, { type: 'string', description: localize('resourceScheme', "The scheme of the resource") }); static readonly Filename = new RawContextKey('resourceFilename', undefined, { type: 'string', description: localize('resourceFilename', "The file name of the resource") }); static readonly Dirname = new RawContextKey('resourceDirname', undefined, { type: 'string', description: localize('resourceDirname', "The folder name the resource is contained in") }); static readonly Path = new RawContextKey('resourcePath', undefined, { type: 'string', description: localize('resourcePath', "The full path of the resource") }); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 166addd5dac..03fd014d39b 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -720,7 +720,7 @@ export interface EditorInputWithPreferredResource { * URIs. But when displaying the editor label to the user, the * preferred URI should be used. * - * Not all editors have a `preferredResouce`. The `EditorResourceAccessor` + * Not all editors have a `preferredResource`. The `EditorResourceAccessor` * utility can be used to always get the right resource without having * to do instanceof checks. */ diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index fc3727f804d..3b400d3fafc 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore } from 'vs/base/common/observable'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; -import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; export class AudioCueLineDebuggerContribution diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index 3c348baa40a..490fab6c4c6 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -12,21 +12,11 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { - autorun, - autorunDelta, - constObservable, - derivedObservable, - observableFromEvent, - observableFromPromise, - IObservable, - wasEventTriggeredRecently, - debouncedObservable, -} from 'vs/workbench/contrib/audioCues/browser/observable'; import { ITextModel } from 'vs/editor/common/model'; import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable'; export class AudioCueLineFeatureContribution extends Disposable @@ -48,7 +38,7 @@ export class AudioCueLineFeatureContribution ) { super(); - const someAudioCueFeatureIsEnabled = derivedObservable( + const someAudioCueFeatureIsEnabled = derived( 'someAudioCueFeatureIsEnabled', (reader) => this.features.some((feature) => @@ -73,7 +63,7 @@ export class AudioCueLineFeatureContribution ); this._register( - autorun((reader) => { + autorun('updateAudioCuesEnabled', (reader) => { this.store.clear(); if (!someAudioCueFeatureIsEnabled.read(reader)) { @@ -84,7 +74,7 @@ export class AudioCueLineFeatureContribution if (activeEditor) { this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store); } - }, 'updateAudioCuesEnabled') + }) ); } @@ -96,6 +86,7 @@ export class AudioCueLineFeatureContribution const curLineNumber = observableFromEvent( editor.onDidChangeCursorPosition, (args) => { + /** @description editor.onDidChangeCursorPosition (caused by user) */ if ( args && args.reason !== CursorChangeReason.Explicit && @@ -117,7 +108,7 @@ export class AudioCueLineFeatureContribution const featureStates = this.features.map((feature) => { const lineFeatureState = feature.getObservableState(editor, editorModel); - const isFeaturePresent = derivedObservable( + const isFeaturePresent = derived( `isPresentInLine:${feature.audioCue.name}`, (reader) => { if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) { @@ -129,7 +120,7 @@ export class AudioCueLineFeatureContribution : lineFeatureState.read(reader).isPresent(lineNumber); } ); - return derivedObservable( + return derived( `typingDebouncedFeatureState:\n${feature.audioCue.name}`, (reader) => feature.debounceWhileTyping && isTyping.read(reader) @@ -138,7 +129,7 @@ export class AudioCueLineFeatureContribution ); }); - const state = derivedObservable( + const state = derived( 'states', (reader) => ({ lineNumber: debouncedLineNumber.read(reader), @@ -193,7 +184,7 @@ class MarkerLineFeature implements LineFeature { Event.filter(this.markerService.onMarkerChanged, (changedUris) => changedUris.some((u) => u.toString() === model.uri.toString()) ), - () => ({ + () => /** @description this.markerService.onMarkerChanged */({ isPresent: (lineNumber) => { const hasMarker = this.markerService .read({ resource: model.uri }) @@ -245,7 +236,7 @@ class BreakpointLineFeature implements LineFeature { getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { return observableFromEvent( this.debugService.getModel().onDidChangeBreakpoints, - () => ({ + () => /** @description debugService.getModel().onDidChangeBreakpoints */({ isPresent: (lineNumber) => { const breakpoints = this.debugService .getModel() @@ -271,17 +262,17 @@ class InlineCompletionLineFeature implements LineFeature { const activeGhostText = observableFromEvent( ghostTextController.onActiveModelDidChange, - () => ghostTextController.activeModel + () => /** @description ghostTextController.onActiveModelDidChange */ ghostTextController.activeModel ).map((activeModel) => ( activeModel ? observableFromEvent( activeModel.inlineCompletionsModel.onDidChange, - () => activeModel.inlineCompletionsModel.ghostText + () => /** @description activeModel.inlineCompletionsModel.onDidChange */ activeModel.inlineCompletionsModel.ghostText ) : undefined )); - return derivedObservable('ghostText', reader => { + return derived('ghostText', reader => { const ghostText = activeGhostText.read(reader)?.read(reader); return { isPresent(lineNumber) { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts index dcac8bf037b..4e491840f5a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts @@ -9,9 +9,9 @@ import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { observableFromEvent, IObservable, LazyDerived } from 'vs/workbench/contrib/audioCues/browser/observable'; import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; +import { IObservable, observableFromEvent, derived } from 'vs/base/common/observable'; export const IAudioCueService = createDecorator('audioCue'); @@ -29,7 +29,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly screenReaderAttached = observableFromEvent( this.accessibilityService.onDidChangeScreenReaderOptimized, - () => this.accessibilityService.isScreenReaderOptimized() + () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() ); constructor( @@ -85,7 +85,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled') ), - () => this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') + () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') ); private readonly isEnabledCache = new Cache((cue: AudioCue) => { @@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { ), () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) ); - return new LazyDerived(reader => { + return derived('audio cue enabled', reader => { const setting = settingObservable.read(reader); if ( setting === 'on' || @@ -113,7 +113,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { } return false; - }, 'audio cue enabled'); + }); }); public isEnabled(cue: AudioCue): IObservable { diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts deleted file mode 100644 index 2ad62412411..00000000000 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ /dev/null @@ -1,690 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; - -export interface IObservable { - _change: TChange; - - /** - * Reads the current value. - * - * This causes a recomputation if needed. - * Calling this method forces changes to propagate to observers during update operations. - * Must not be called from {@link IObserver.handleChange}. - */ - get(): T; - - /** - * Registers an observer. - * - * Calls {@link IObserver.handleChange} immediately after a change is noticed. - * Might happen while someone calls {@link IObservable.get} or {@link IObservable.read}. - */ - subscribe(observer: IObserver): void; - unsubscribe(observer: IObserver): void; - - /** - * Calls {@link IObservable.get} and then {@link IReader.handleBeforeReadObservable}. - */ - read(reader: IReader): T; - - map(fn: (value: T) => TNew): IObservable; -} - -export interface IReader { - /** - * Reports an observable that was read. - * - * Is called by `Observable.read`. - */ - handleBeforeReadObservable(observable: IObservable): void; -} - -export interface IObserver { - /** - * Indicates that an update operation is about to begin. - * - * During an update, invariants might not hold for subscribed observables and - * change events might be delayed. - * However, all changes must be reported before all update operations are over. - */ - beginUpdate(observable: IObservable): void; - - /** - * Is called by a subscribed observable immediately after it notices a change. - * - * When {@link IObservable.get} returns and no change has been reported, - * there has been no change for that observable. - * - * Implementations must not call into other observables! - * The change should be processed when {@link IObserver.endUpdate} is called. - */ - handleChange(observable: IObservable, change: TChange): void; - - /** - * Indicates that an update operation has completed. - */ - endUpdate(observable: IObservable): void; -} - -export interface ISettable { - set(value: T, transaction: ITransaction | undefined, change: TChange): void; -} - -export interface ITransaction { - /** - * Calls `Observer.beginUpdate` immediately - * and `Observer.endUpdate` when the transaction is complete. - */ - updateObserver( - observer: IObserver, - observable: IObservable - ): void; -} - -// === Base === -export abstract class ConvenientObservable implements IObservable { - get _change(): TChange { return null!; } - - public abstract get(): T; - public abstract subscribe(observer: IObserver): void; - public abstract unsubscribe(observer: IObserver): void; - - public read(reader: IReader): T { - reader.handleBeforeReadObservable(this); - return this.get(); - } - - public map(fn: (value: T) => TNew): IObservable { - return new LazyDerived((reader) => fn(this.read(reader)), '(mapped)'); - } -} - -export abstract class BaseObservable extends ConvenientObservable { - protected readonly observers = new Set(); - - public subscribe(observer: IObserver): void { - const len = this.observers.size; - this.observers.add(observer); - if (len === 0) { - this.onFirstObserverSubscribed(); - } - } - - public unsubscribe(observer: IObserver): void { - const deleted = this.observers.delete(observer); - if (deleted && this.observers.size === 0) { - this.onLastObserverUnsubscribed(); - } - } - - protected onFirstObserverSubscribed(): void { } - protected onLastObserverUnsubscribed(): void { } -} - -export function transaction(fn: (tx: ITransaction) => void) { - const tx = new TransactionImpl(); - try { - fn(tx); - } finally { - tx.finish(); - } -} - -class TransactionImpl implements ITransaction { - private readonly finishActions = new Array<() => void>(); - - public updateObserver( - observer: IObserver, - observable: IObservable - ): void { - this.finishActions.push(function () { - observer.endUpdate(observable); - }); - observer.beginUpdate(observable); - } - - public finish(): void { - for (const action of this.finishActions) { - action(); - } - } -} - -export class ObservableValue - extends BaseObservable - implements ISettable -{ - private value: T; - - constructor(initialValue: T, public readonly name: string) { - super(); - this.value = initialValue; - } - - public get(): T { - return this.value; - } - - public set(value: T, tx: ITransaction | undefined, change: TChange): void { - if (this.value === value) { - return; - } - - if (!tx) { - transaction((tx) => { - this.set(value, tx, change); - }); - return; - } - - this.value = value; - - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, change); - } - } -} - -export function constObservable(value: T): IObservable { - return new ConstObservable(value); -} - -class ConstObservable extends ConvenientObservable { - constructor(private readonly value: T) { - super(); - } - - public get(): T { - return this.value; - } - public subscribe(observer: IObserver): void { - // NO OP - } - public unsubscribe(observer: IObserver): void { - // NO OP - } -} - -// == autorun == -export function autorun(fn: (reader: IReader) => void, name: string): IDisposable { - return new AutorunObserver(fn, name, undefined); -} - -interface IChangeContext { - readonly changedObservable: IObservable; - readonly change: unknown; - - didChange(observable: IObservable): this is { change: TChange }; -} - -export function autorunHandleChanges( - name: string, - options: { - /** - * Returns if this change should cause a re-run of the autorun. - */ - handleChange: (context: IChangeContext) => boolean; - }, - fn: (reader: IReader) => void -): IDisposable { - return new AutorunObserver(fn, name, options.handleChange); -} - -export function autorunWithStore( - fn: (reader: IReader, store: DisposableStore) => void, - name: string -): IDisposable { - const store = new DisposableStore(); - const disposable = autorun( - reader => { - store.clear(); - fn(reader, store); - }, - name - ); - return toDisposable(() => { - disposable.dispose(); - store.dispose(); - }); -} - -export class AutorunObserver implements IObserver, IReader, IDisposable { - public needsToRun = true; - private updateCount = 0; - - /** - * The actual dependencies. - */ - private _dependencies = new Set>(); - public get dependencies() { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly runFn: (reader: IReader) => void, - public readonly name: string, - private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined - ) { - this.runIfNeeded(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange(observable: IObservable, change: TChange): void { - const shouldReact = this._handleChange ? this._handleChange({ - changedObservable: observable, - change, - didChange: o => o === observable as any, - }) : true; - this.needsToRun = this.needsToRun || shouldReact; - - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - public beginUpdate() { - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - private runIfNeeded(): void { - if (!this.needsToRun) { - return; - } - // Assert: this.staleDependencies is an empty set. - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - this.needsToRun = false; - - try { - this.runFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - } - - public dispose() { - for (const o of this._dependencies) { - o.unsubscribe(this); - } - this._dependencies.clear(); - } -} - -export namespace autorun { - export const Observer = AutorunObserver; -} -export function autorunDelta( - name: string, - observable: IObservable, - handler: (args: { lastValue: T | undefined; newValue: T }) => void -): IDisposable { - let _lastValue: T | undefined; - return autorun((reader) => { - const newValue = observable.read(reader); - const lastValue = _lastValue; - _lastValue = newValue; - handler({ lastValue, newValue }); - }, name); -} - - -// == Lazy Derived == - -export function derivedObservable(name: string, computeFn: (reader: IReader) => T): IObservable { - return new LazyDerived(computeFn, name); -} -export class LazyDerived extends ConvenientObservable { - private readonly observer: LazyDerivedObserver; - - constructor(computeFn: (reader: IReader) => T, name: string) { - super(); - this.observer = new LazyDerivedObserver(computeFn, name, this); - } - - public subscribe(observer: IObserver): void { - this.observer.subscribe(observer); - } - - public unsubscribe(observer: IObserver): void { - this.observer.unsubscribe(observer); - } - - public override read(reader: IReader): T { - return this.observer.read(reader); - } - - public get(): T { - return this.observer.get(); - } -} - -/** - * @internal - */ -class LazyDerivedObserver - extends BaseObservable - implements IReader, IObserver { - private hadValue = false; - private hasValue = false; - private value: T | undefined = undefined; - private updateCount = 0; - - private _dependencies = new Set>(); - public get dependencies(): ReadonlySet> { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly computeFn: (reader: IReader) => T, - public readonly name: string, - private readonly actualObservable: LazyDerived, - ) { - super(); - } - - protected override onLastObserverUnsubscribed(): void { - /** - * We are not tracking changes anymore, thus we have to assume - * that our cache is invalid. - */ - this.hasValue = false; - this.hadValue = false; - this.value = undefined; - for (const d of this._dependencies) { - d.unsubscribe(this); - } - this._dependencies.clear(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange() { - if (this.hasValue) { - this.hadValue = true; - this.hasValue = false; - } - - // Not in transaction: Recompute & inform observers immediately - if (this.updateCount === 0 && this.observers.size > 0) { - this.get(); - } - - // Otherwise, recompute in `endUpdate` or on demand. - } - - public beginUpdate() { - if (this.updateCount === 0) { - for (const r of this.observers) { - r.beginUpdate(this); - } - } - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - if (this.observers.size > 0) { - // Propagate invalidation - this.get(); - } - - for (const r of this.observers) { - r.endUpdate(this); - } - } - } - - public get(): T { - if (this.observers.size === 0) { - // Cache is not valid and don't refresh the cache. - // Observables should not be read in non-reactive contexts. - return this.computeFn(this); - } - - if (this.updateCount > 0 && this.hasValue) { - // Refresh dependencies - for (const d of this._dependencies) { - // Maybe `.get()` triggers `handleChange`? - d.get(); - if (!this.hasValue) { - // The other dependencies will refresh on demand - break; - } - } - } - - if (!this.hasValue) { - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - const oldValue = this.value; - try { - this.value = this.computeFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - - this.hasValue = true; - if (this.hadValue && oldValue !== this.value) { - for (const r of this.observers) { - r.handleChange(this.actualObservable, undefined); - } - } - } - return this.value!; - } -} - -export namespace LazyDerived { - export const Observer = LazyDerivedObserver; -} - -export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { - const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue'); - promise.then((value) => { - observable.set({ value }, undefined); - }); - return observable; -} - -export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { - return new Promise(resolve => { - const d = autorun(reader => { - const currentState = observable.read(reader); - if (predicate(currentState)) { - d.dispose(); - resolve(currentState); - } - }, 'waitForState'); - }); -} - -export function observableFromEvent( - event: Event, - getValue: (args: TArgs | undefined) => T -): IObservable { - return new FromEventObservable(event, getValue); -} - -class FromEventObservable extends BaseObservable { - private value: T | undefined; - private hasValue = false; - private subscription: IDisposable | undefined; - - constructor( - private readonly event: Event, - private readonly getValue: (args: TArgs | undefined) => T - ) { - super(); - } - - protected override onFirstObserverSubscribed(): void { - this.subscription = this.event(this.handleEvent); - } - - private readonly handleEvent = (args: TArgs | undefined) => { - const newValue = this.getValue(args); - if (this.value !== newValue) { - this.value = newValue; - - if (this.hasValue) { - transaction(tx => { - for (const o of this.observers) { - tx.updateObserver(o, this); - o.handleChange(this, undefined); - } - }); - } - this.hasValue = true; - } - }; - - protected override onLastObserverUnsubscribed(): void { - this.subscription!.dispose(); - this.subscription = undefined; - this.hasValue = false; - this.value = undefined; - } - - public get(): T { - if (this.subscription) { - if (!this.hasValue) { - this.handleEvent(undefined); - } - return this.value!; - } else { - // no cache, as there are no subscribers to clean it up - return this.getValue(undefined); - } - } -} - -export namespace observableFromEvent { - export const Observer = FromEventObservable; -} - -export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { - const debouncedObservable = new ObservableValue(undefined, 'debounced'); - - let timeout: any = undefined; - - disposableStore.add(autorun(reader => { - const value = observable.read(reader); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - transaction(tx => { - debouncedObservable.set(value, tx); - }); - }, debounceMs); - - }, 'debounce')); - - return debouncedObservable; -} - -export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { - const observable = new ObservableValue(false, 'triggeredRecently'); - - let timeout: any = undefined; - - disposableStore.add(event(() => { - observable.set(true, undefined); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - observable.set(false, undefined); - }, timeoutMs); - })); - - return observable; -} - -/** - * This ensures the observable is kept up-to-date. - * This is useful when the observables `get` method is used. -*/ -export function keepAlive(observable: IObservable): IDisposable { - return autorun(reader => { - observable.read(reader); - }, 'keep-alive'); -} - -export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { - let lastValue: T | undefined = undefined; - const observable = derivedObservable(name, reader => { - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return observable; -} - -export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { - let lastValue: T | undefined = undefined; - const counter = new ObservableValue(0, 'counter'); - const observable = derivedObservable(name, reader => { - counter.read(reader); - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return Object.assign(observable, { - clearCache: (transaction: ITransaction) => { - lastValue = undefined; - counter.set(counter.get() + 1, transaction); - }, - }); -} diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts index 1f533a82ccc..0e870b7b639 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts @@ -6,20 +6,36 @@ import { groupBy } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { compare } from 'vs/base/common/strings'; +import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEditMetadata } from 'vs/editor/common/languages'; import { IProgress } from 'vs/platform/progress/common/progress'; import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; -import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellPartialMetadataEdit, ICellReplaceEdit, IDocumentMetadataEdit, IWorkspaceNotebookCellEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -export class ResourceNotebookCellEdit extends ResourceEdit { +export class ResourceNotebookCellEdit extends ResourceEdit implements IWorkspaceNotebookCellEdit { + + static is(candidate: any): candidate is IWorkspaceNotebookCellEdit { + if (candidate instanceof ResourceNotebookCellEdit) { + return true; + } + return URI.isUri((candidate).resource) + && isObject((candidate).cellEdit); + } + + static lift(edit: IWorkspaceNotebookCellEdit): ResourceNotebookCellEdit { + if (edit instanceof ResourceNotebookCellEdit) { + return edit; + } + return new ResourceNotebookCellEdit(edit.resource, edit.cellEdit, edit.notebookVersionId, edit.metadata); + } constructor( readonly resource: URI, - readonly cellEdit: ICellEditOperation, - readonly versionId?: number, + readonly cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit, + readonly notebookVersionId: number | undefined = undefined, metadata?: WorkspaceEditMetadata ) { super(metadata); @@ -49,7 +65,7 @@ export class BulkCellEdits { const ref = await this._notebookModelService.resolve(first.resource); // check state - if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) { + if (typeof first.notebookVersionId === 'number' && ref.object.notebook.versionId !== first.notebookVersionId) { ref.dispose(); throw new Error(`Notebook '${first.resource}' has changed in the meantime`); } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index f69e60201f2..6906a0b4fb7 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -19,8 +19,9 @@ import { ResourceMap } from 'vs/base/common/map'; import { IModelService } from 'vs/editor/common/services/model'; import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { performSnippetEdits } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { ISnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetSession'; type ValidationResult = { canApply: true } | { canApply: false; reason: URI }; @@ -115,7 +116,7 @@ class ModelEditTask implements IDisposable { if (!edit.text) { return edit; } - const text = new SnippetParser().parse(edit.text, false, false).toString(); + const text = new SnippetParser().text(edit.text); return { ...edit, insertAsSnippet: false, text }; } } @@ -141,14 +142,24 @@ class EditorEditTask extends ModelEditTask { super.apply(); return; } - if (this._edits.length > 0) { - const insertAsSnippet = this._edits.every(edit => edit.insertAsSnippet); - if (insertAsSnippet) { - // todo@jrieken what ABOUT EOL? - performSnippetEdits(this._editor, this._edits.map(edit => ({ range: Range.lift(edit.range!), snippet: edit.text! }))); + if (this._edits.length > 0) { + const snippetCtrl = SnippetController2.get(this._editor); + if (snippetCtrl && this._edits.some(edit => edit.insertAsSnippet)) { + // some edit is a snippet edit -> use snippet controller and ISnippetEdits + const snippetEdits: ISnippetEdit[] = []; + for (const edit of this._edits) { + if (edit.range && edit.text !== null) { + snippetEdits.push({ + range: Range.lift(edit.range), + template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text) + }); + } + } + snippetCtrl.apply(snippetEdits); } else { + // normal edit this._edits = this._edits .map(this._transformSnippetStringToInsertText, this) // mixed edits (snippet and normal) -> no snippet mode .sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); @@ -222,13 +233,13 @@ export class BulkTextEdits { let makeMinimal = false; if (this._editor?.getModel()?.uri.toString() === ref.object.textEditorModel.uri.toString()) { task = new EditorEditTask(ref, this._editor); - makeMinimal = true && false; // todo@jrieken HACK + makeMinimal = true; } else { task = new ModelEditTask(ref); } for (const edit of value) { - if (makeMinimal) { + if (makeMinimal && !edit.textEdit.insertAsSnippet) { const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, [edit.textEdit]); if (!newEdits) { task.addEdit(edit); diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 21018be3fee..bc5c6adca32 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -330,7 +330,7 @@ export abstract class SimpleFindWidget extends Widget { this._matchesCount.innerText = ''; let label = ''; this._matchesCount.classList.toggle('no-results', false); - if (count?.resultCount && count?.resultCount <= 0) { + if (count?.resultCount !== undefined && count?.resultCount === 0) { label = NLS_NO_RESULTS; if (!!this.inputValue) { this._matchesCount.classList.toggle('no-results', true); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index d1d118f5c15..450c76ae8ad 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -382,7 +382,7 @@ export class CommentNode extends Disposable { private createCommentEditor(editContainer: HTMLElement): void { const container = dom.append(editContainer, dom.$('.edit-textarea')); - this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentThread); + this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(this.configurationService), this.parentThread); const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`); this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false); diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 87b195c52f8..8fe2a07420e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -17,6 +17,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; @@ -58,11 +59,12 @@ export class CommentReply extends Disposable { @ILanguageService private languageService: ILanguageService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService ) { super(); this.form = dom.append(container, dom.$('.comment-form')); - this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(), this._parentThread)); + this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(configurationService), this._parentThread)); this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); this.commentEditorIsEmpty.set(!this._pendingComment); @@ -191,7 +193,7 @@ export class CommentReply extends Disposable { } }]; - this.commentEditor.setDecorations('review-zone-widget', COMMENTEDITOR_DECORATION_KEY, decorations); + this.commentEditor.setDecorationsByType('review-zone-widget', COMMENTEDITOR_DECORATION_KEY, decorations); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts index 973762aa995..25b62cec998 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts @@ -84,8 +84,10 @@ export class CommentThreadRangeDecorator extends Disposable { } } } - this.activeDecorationIds = this.editor.deltaDecorations(this.activeDecorationIds, newDecoration); - newDecoration.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + this.editor.changeDecorations((changeAccessor) => { + this.activeDecorationIds = changeAccessor.deltaDecorations(this.activeDecorationIds, newDecoration); + newDecoration.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + }); } public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) { @@ -122,8 +124,10 @@ export class CommentThreadRangeDecorator extends Disposable { }); } - this.decorationIds = editor.deltaDecorations(this.decorationIds, commentThreadRangeDecorations); - commentThreadRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + editor.changeDecorations((changeAccessor) => { + this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, commentThreadRangeDecorations); + commentThreadRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]); + }); } override dispose() { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 32b36c7f42f..99c4aae1ca9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -56,6 +56,22 @@ export function parseMouseDownInfoFromEvent(e: IEditorMouseEvent) { return { lineNumber: range.startLineNumber }; } +export function isMouseUpEventDragFromMouseDown(mouseDownInfo: { lineNumber: number } | null, e: IEditorMouseEvent) { + if (!mouseDownInfo) { + return null; + } + + const { lineNumber } = mouseDownInfo; + + const range = e.target.range; + + if (!range) { + return null; + } + + return lineNumber; +} + export function isMouseUpEventMatchMouseDown(mouseDownInfo: { lineNumber: number } | null, e: IEditorMouseEvent) { if (!mouseDownInfo) { return null; diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 04c4381ce3f..41ed64d6d55 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -31,7 +31,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic import { STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; import { CommentGlyphWidget, overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { isMouseUpEventMatchMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; +import { isMouseUpEventDragFromMouseDown, parseMouseDownInfoFromEvent, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadZoneWidget'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -382,6 +382,7 @@ export class CommentController implements IEditorContribution { this._commentingRangeDecorator.update(this.editor, []); this._commentThreadRangeDecorator.update(this.editor, []); dispose(this._commentWidgets); + this._commentWidgets = []; } })); @@ -405,7 +406,12 @@ export class CommentController implements IEditorContribution { } private onEditorMouseMove(e: IEditorMouseEvent): void { - this._commentingRangeDecorator.updateHover(e.target.position?.lineNumber); + const position = e.target.position?.lineNumber; + if (e.event.leftButton.valueOf() && position && this.mouseDownInfo) { + this._commentingRangeDecorator.updateSelection(position, new Range(this.mouseDownInfo.lineNumber, 1, position, 1)); + } else { + this._commentingRangeDecorator.updateHover(position); + } } private onEditorChangeCursorSelection(e?: ICursorSelectionChangedEvent): void { @@ -691,22 +697,37 @@ export class CommentController implements IEditorContribution { } private onEditorMouseUp(e: IEditorMouseEvent): void { - const matchedLineNumber = isMouseUpEventMatchMouseDown(this.mouseDownInfo, e); + const matchedLineNumber = isMouseUpEventDragFromMouseDown(this.mouseDownInfo, e); this.mouseDownInfo = null; if (matchedLineNumber === null || !e.target.element) { return; } + const mouseUpIsOnDecorator = (e.target.element.className.indexOf('comment-diff-added') >= 0); - if (e.target.element.className.indexOf('comment-diff-added') >= 0) { - const lineNumber = e.target.position!.lineNumber; - // Check for selection at line number. - let range: Range = new Range(lineNumber, 1, lineNumber, 1); - const selection = this.editor.getSelection(); - if (selection && (selection.startLineNumber <= lineNumber) && (lineNumber <= selection.endLineNumber)) { - range = selection; - this.editor.setSelection(new Range(selection.endLineNumber, 1, selection.endLineNumber, 1)); + const lineNumber = e.target.position!.lineNumber; + let range: Range | undefined; + let selection: Range | null | undefined; + // Check for drag along gutter decoration + if ((matchedLineNumber !== lineNumber)) { + if (matchedLineNumber > lineNumber) { + selection = new Range(matchedLineNumber, this.editor.getModel()!.getLineLength(matchedLineNumber) + 1, lineNumber, 1); + } else { + selection = new Range(matchedLineNumber, 1, lineNumber, this.editor.getModel()!.getLineLength(lineNumber) + 1); } + } else if (mouseUpIsOnDecorator) { + selection = this.editor.getSelection(); + } + + // Check for selection at line number. + if (selection && (selection.startLineNumber <= lineNumber) && (lineNumber <= selection.endLineNumber)) { + range = selection; + this.editor.setSelection(new Range(selection.endLineNumber, 1, selection.endLineNumber, 1)); + } else if (mouseUpIsOnDecorator) { + range = new Range(lineNumber, 1, lineNumber, 1); + } + + if (range) { this.addOrToggleCommentAtLine(range, e); } } @@ -839,7 +860,7 @@ export class CommentController implements IEditorContribution { } const options = this.editor.getOptions(); - if (options.get(EditorOption.folding)) { + if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') { lineDecorationsWidth -= 16; } lineDecorationsWidth += 9; diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index d339596f24d..4c38db0a895 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -215,6 +215,10 @@ export class CommentsPanel extends ViewPane { return false; } + if (!this.commentService.isCommentingEnabled) { + this.commentService.enableCommenting(true); + } + const range = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].range : element.range; const activeEditor = this.editorService.activeTextEditorControl; @@ -230,7 +234,7 @@ export class CommentsPanel extends ViewPane { const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; if (threadToReveal && isCodeEditor(editor)) { const controller = CommentController.get(editor); - controller?.revealCommentThread(threadToReveal, commentToReveal, false); + controller?.revealCommentThread(threadToReveal, commentToReveal, true); } return true; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 4a69955fd4a..c4ec1bbf5e3 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -24,6 +24,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); @@ -79,7 +80,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { return EditorExtensionsRegistry.getEditorActions(); } - public static getEditorOptions(): IEditorOptions { + public static getEditorOptions(configurationService: IConfigurationService): IEditorOptions { return { wordWrap: 'on', glyphMargin: false, @@ -103,6 +104,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { minimap: { enabled: false }, + autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'), quickSuggestions: false }; } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 7ed46f240ef..8fdc2c1f303 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -507,26 +507,28 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi this.ignoreDecorationsChangedEvent = true; // Set breakpoint decorations - const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); - this.breakpointDecorations.forEach(bpd => { - if (bpd.inlineWidget) { - bpd.inlineWidget.dispose(); - } - }); - this.breakpointDecorations = decorationIds.map((decorationId, index) => { - let inlineWidget: InlineBreakpointWidget | undefined = undefined; - const breakpoint = breakpoints[index]; - if (desiredBreakpointDecorations[index].options.before) { - const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); - inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); - } + activeCodeEditor.changeDecorations((changeAccessor) => { + const decorationIds = changeAccessor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); + this.breakpointDecorations.forEach(bpd => { + if (bpd.inlineWidget) { + bpd.inlineWidget.dispose(); + } + }); + this.breakpointDecorations = decorationIds.map((decorationId, index) => { + let inlineWidget: InlineBreakpointWidget | undefined = undefined; + const breakpoint = breakpoints[index]; + if (desiredBreakpointDecorations[index].options.before) { + const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); + } - return { - decorationId, - breakpoint, - range: desiredBreakpointDecorations[index].range, - inlineWidget - }; + return { + decorationId, + breakpoint, + range: desiredBreakpointDecorations[index].range, + inlineWidget + }; + }); }); } finally { this.ignoreDecorationsChangedEvent = false; @@ -535,23 +537,25 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi // Set breakpoint candidate decorations const session = this.debugService.getViewModel().focusedSession; const desiredCandidateDecorations = debugSettings.showInlineBreakpointCandidates && session ? await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, session) : []; - const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); - this.candidateDecorations.forEach(candidate => { - candidate.inlineWidget.dispose(); - }); - this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { - const candidate = desiredCandidateDecorations[index]; - // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there - // In practice this happens for the first breakpoint that was set on a line - // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information - const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; - const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); - const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); + this.editor.changeDecorations((changeAccessor) => { + const candidateDecorationIds = changeAccessor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecorations.forEach(candidate => { + candidate.inlineWidget.dispose(); + }); + this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { + const candidate = desiredCandidateDecorations[index]; + // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there + // In practice this happens for the first breakpoint that was set on a line + // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information + const icon = candidate.breakpoint ? getBreakpointMessageAndIcon(this.debugService.state, this.debugService.getModel().areBreakpointsActivated(), candidate.breakpoint, this.labelService).icon : icons.breakpoint.disabled; + const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, ThemeIcon.asClassName(icon), candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); - return { - decorationId, - inlineWidget - }; + return { + decorationId, + inlineWidget + }; + }); }); for (const d of this.breakpointDecorations) { @@ -631,7 +635,7 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi if (this.breakpointWidget) { this.breakpointWidget.dispose(); } - this.editor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), []); + this.editor.removeDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId)); dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 3df0cf71a74..fb873a5fc69 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -238,7 +238,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const setDecorations = () => { const value = this.input.getModel().getValue(); const decorations = !!value ? [] : createDecorations(this.themeService.getColorTheme(), this.placeholder); - this.input.setDecorations('breakpoint-widget', DECORATION_KEY, decorations); + this.input.setDecorationsByType('breakpoint-widget', DECORATION_KEY, decorations); }; this.input.getModel().onDidChangeContent(() => setDecorations()); this.themeService.onDidColorThemeChange(() => setDecorations()); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 2eb3d65a818..b97d040674f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -21,7 +21,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle' import { posix } from 'vs/base/common/path'; import { commonSuffixLength } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; -import { Icon } from 'vs/platform/action/common/action'; +import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -1120,7 +1120,7 @@ registerAction2(class Collapse extends ViewAction { } }); -function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { +function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { group: 'inline', order, diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3b56252432d..54d0f0a0e7c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -55,7 +55,7 @@ import { DisassemblyView, DisassemblyViewContribution } from 'vs/workbench/contr import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; -import { Icon } from 'vs/platform/action/common/action'; +import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess'; @@ -98,20 +98,21 @@ registerEditorContribution('editor.contrib.callStack', CallStackEditorContributi registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution); -const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { +const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, when), group: debugCategory, command: { id, - title: `Debug: ${title}`, + title, + category: DEBUG_COMMAND_CATEGORY, precondition } }); }; registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL); -registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, { value: nls.localize('terminateThread', "Terminate Thread"), original: 'Terminate Thread' }, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.and(CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); @@ -121,23 +122,27 @@ registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBU registerDebugCommandPaletteItem(DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, ContextKeyExpr.and(CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED))); registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)); registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View')); -registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); -registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); -registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); -registerDebugCommandPaletteItem(SelectionToReplAction.ID, SelectionToReplAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); -registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, SelectionToWatchExpressionsAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); -registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint")); +registerDebugCommandPaletteItem(FOCUS_REPL_ID, { value: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'), original: 'Focus on Debug Console View' }); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('jumpToCursor', "Jump to Cursor"), original: 'Jump to Cursor' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('SetNextStatement', "Set Next Statement"), original: 'Set Next Statement' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(RunToCursorAction.ID, { value: RunToCursorAction.LABEL, original: 'Run to Cursor' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); +registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); +registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); +registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' }); registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL); +registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL); - +registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu -const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { +const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string | ICommandActionTitle, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { MenuRegistry.appendMenuItem(menuId, { group, when, @@ -185,7 +190,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COM // Touch Bar if (isMacintosh) { - const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { + const registerTouchBarEntry = (id: string, title: string | ICommandActionTitle, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { id, diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index d5524d02f0a..596bbd9c32d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_REPL, CONTEXT_STEP_INTO_TARGETS_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -31,6 +31,7 @@ import { deepClone } from 'vs/base/common/objects'; import { isWeb, isWindows } from 'vs/base/common/platform'; import { saveAllBeforeDebugStart } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { showLoadedScriptMenu } from 'vs/workbench/contrib/debug/common/loadedScriptsPicker'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -62,25 +63,37 @@ export const SET_EXPRESSION_COMMAND_ID = 'debug.setWatchExpression'; export const REMOVE_EXPRESSION_COMMAND_ID = 'debug.removeWatchExpression'; export const NEXT_DEBUG_CONSOLE_ID = 'workbench.action.debug.nextConsole'; export const PREV_DEBUG_CONSOLE_ID = 'workbench.action.debug.prevConsole'; +export const SHOW_LOADED_SCRIPTS_ID = 'workbench.action.debug.showLoadedScripts'; +export const CALLSTACK_TOP_ID = 'workbench.action.debug.callStackTop'; +export const CALLSTACK_BOTTOM_ID = 'workbench.action.debug.callStackBottom'; +export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp'; +export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown'; -export const RESTART_LABEL = nls.localize('restartDebug', "Restart"); -export const STEP_OVER_LABEL = nls.localize('stepOverDebug', "Step Over"); -export const STEP_INTO_LABEL = nls.localize('stepIntoDebug', "Step Into"); -export const STEP_INTO_TARGET_LABEL = nls.localize('stepIntoTargetDebug', "Step Into Target"); -export const STEP_OUT_LABEL = nls.localize('stepOutDebug', "Step Out"); -export const PAUSE_LABEL = nls.localize('pauseDebug', "Pause"); -export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); -export const DISCONNECT_AND_SUSPEND_LABEL = nls.localize('disconnectSuspend', "Disconnect and Suspend"); -export const STOP_LABEL = nls.localize('stop', "Stop"); -export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -export const FOCUS_SESSION_LABEL = nls.localize('focusSession', "Focus Session"); -export const SELECT_AND_START_LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); +export const DEBUG_COMMAND_CATEGORY = 'Debug'; +export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' }; +export const STEP_OVER_LABEL = { value: nls.localize('stepOverDebug', "Step Over"), original: 'Step Over' }; +export const STEP_INTO_LABEL = { value: nls.localize('stepIntoDebug', "Step Into"), original: 'Step Into' }; +export const STEP_INTO_TARGET_LABEL = { value: nls.localize('stepIntoTargetDebug', "Step Into Target"), original: 'Step Into Target' }; +export const STEP_OUT_LABEL = { value: nls.localize('stepOutDebug', "Step Out"), original: 'Step Out' }; +export const PAUSE_LABEL = { value: nls.localize('pauseDebug', "Pause"), original: 'Pause' }; +export const DISCONNECT_LABEL = { value: nls.localize('disconnect', "Disconnect"), original: 'Disconnect' }; +export const DISCONNECT_AND_SUSPEND_LABEL = { value: nls.localize('disconnectSuspend', "Disconnect and Suspend"), original: 'Disconnect and Suspend' }; +export const STOP_LABEL = { value: nls.localize('stop', "Stop"), original: 'Stop' }; +export const CONTINUE_LABEL = { value: nls.localize('continueDebug', "Continue"), original: 'Continue' }; +export const FOCUS_SESSION_LABEL = { value: nls.localize('focusSession', "Focus Session"), original: 'Focus Session' }; +export const SELECT_AND_START_LABEL = { value: nls.localize('selectAndStartDebugging', "Select and Start Debugging"), original: 'Select and Start Debugging' }; export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open '{0}'", 'launch.json'); -export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging"); -export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); -export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize('nextDebugConsole', "Focus Next Debug Console"); -export const PREV_DEBUG_CONSOLE_LABEL = nls.localize('prevDebugConsole', "Focus Previous Debug Console"); -export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize('selectDebugConsole', "Select Debug Console"); +export const DEBUG_START_LABEL = { value: nls.localize('startDebug', "Start Debugging"), original: 'Start Debugging' }; +export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "Start Without Debugging"), original: 'Start Without Debugging' }; +export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' }; +export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' }; +export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' }; +export const CALLSTACK_TOP_LABEL = { value: nls.localize('callStackTop', "Navigate to Top of Call Stack"), original: 'Navigate to Top of Call Stack' }; +export const CALLSTACK_BOTTOM_LABEL = { value: nls.localize('callStackBottom', "Navigate to Bottom of Call Stack"), original: 'Navigate to Bottom of Call Stack' }; +export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate Up Call Stack"), original: 'Navigate Up Call Stack' }; +export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' }; + +export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' }; export const DEBUG_QUICK_ACCESS_PREFIX = 'debug '; export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles '; @@ -175,6 +188,103 @@ async function changeDebugConsoleFocus(accessor: ServicesAccessor, next: boolean } } +async function navigateCallStack(debugService: IDebugService, down: boolean) { + const frame = debugService.getViewModel().focusedStackFrame; + if (frame) { + + let callStack = frame.thread.getCallStack(); + let index = callStack.findIndex(elem => elem.frameId === frame.frameId); + let nextVisibleFrame; + if (down) { + if (index >= callStack.length - 1) { + if ((frame.thread).reachedEndOfCallStack) { + goToTopOfCallStack(debugService); + return; + } else { + await debugService.getModel().fetchCallstack(frame.thread, 20); + callStack = frame.thread.getCallStack(); + index = callStack.findIndex(elem => elem.frameId === frame.frameId); + } + } + nextVisibleFrame = findNextVisibleFrame(true, callStack, index); + } else { + if (index <= 0) { + goToBottomOfCallStack(debugService); + return; + } + nextVisibleFrame = findNextVisibleFrame(false, callStack, index); + } + + if (nextVisibleFrame) { + debugService.focusStackFrame(nextVisibleFrame); + } + } +} + +async function goToBottomOfCallStack(debugService: IDebugService) { + const thread = debugService.getViewModel().focusedThread; + if (thread) { + await debugService.getModel().fetchCallstack(thread); + const callStack = thread.getCallStack(); + if (callStack.length > 0) { + const nextVisibleFrame = findNextVisibleFrame(false, callStack, 0); // must consider the next frame up first, which will be the last frame + if (nextVisibleFrame) { + debugService.focusStackFrame(nextVisibleFrame); + } + } + } +} + +function goToTopOfCallStack(debugService: IDebugService) { + const thread = debugService.getViewModel().focusedThread; + + if (thread) { + debugService.focusStackFrame(thread.getTopStackFrame()); + } +} + +/** + * Finds next frame that is not skipped by SkipFiles. Skips frame at index and starts searching at next. + * Must satisfy `0 <= startIndex <= callStack - 1` + * @param down specifies whether to search downwards if the current file is skipped. + * @param callStack the call stack to search + * @param startIndex the index to start the search at + */ +function findNextVisibleFrame(down: boolean, callStack: readonly IStackFrame[], startIndex: number) { + + if (startIndex >= callStack.length) { + startIndex = callStack.length - 1; + } else if (startIndex < 0) { + startIndex = 0; + } + + let index = startIndex; + + let currFrame; + do { + if (down) { + if (index === callStack.length - 1) { + index = 0; + } else { + index++; + } + } else { + if (index === 0) { + index = callStack.length - 1; + } else { + index--; + } + } + + currFrame = callStack[index]; + if (!(currFrame.source.presentationHint === 'deemphasize' || currFrame.presentationHint === 'deemphasize')) { + return currFrame; + } + } while (index !== startIndex); // end loop when we've just checked the start index, since that should be the last one checked + + return undefined; +} + // These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar // When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id // Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread @@ -256,6 +366,39 @@ CommandsRegistry.registerCommand({ } }); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_TOP_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + goToTopOfCallStack(debugService); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_BOTTOM_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + await goToBottomOfCallStack(debugService); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_UP_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + navigateCallStack(debugService, false); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_DOWN_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + navigateCallStack(debugService, true); + } +}); + MenuRegistry.appendMenuItem(MenuId.EditorContext, { command: { id: JUMP_TO_CURSOR_ID, @@ -511,6 +654,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +CommandsRegistry.registerCommand({ + id: SHOW_LOADED_SCRIPTS_ID, + handler: async (accessor) => { + + await showLoadedScriptMenu(accessor); + + } +}); + CommandsRegistry.registerCommand({ id: FOCUS_REPL_ID, handler: async (accessor) => { @@ -571,7 +723,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; const config = await getConfig(); const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name; - await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false); + await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true, saveBeforeStart: false }); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts index 370e400370f..5479a5a393c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConsoleQuickAccess.ts @@ -11,7 +11,7 @@ import { FastAndSlowPicks, IPickerQuickAccessItem, PickerQuickAccessProvider, Pi import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IViewsService } from 'vs/workbench/common/views'; import { DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_AND_START_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { getStateLabel, IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IDebugSession, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; export class DebugConsoleQuickAccess extends PickerQuickAccessProvider { @@ -54,7 +54,6 @@ export class DebugConsoleQuickAccess extends PickerQuickAccessProvider { this._debugService.focusStackFrame(undefined, undefined, session, { explicit: true }); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b308716a8bb..2474922a5ad 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -312,7 +312,10 @@ export class DebugService implements IDebugService { * main entry point * properly manages compounds, checks for errors and handles the initializing state. */ - async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise { + async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { + + const saveBeforeStart = options?.saveBeforeStart ?? !options?.parentSession; + const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace."); const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message }); if (!trust) { @@ -701,7 +704,10 @@ export class DebugService implements IDebugService { } async restartSession(session: IDebugSession, restartData?: any): Promise { - await this.editorService.saveAll(); + if (session.saveBeforeStart) { + await saveAllBeforeDebugStart(this.configurationService, this.editorService); + } + const isAutoRestart = !!restartData; const runTasks: () => Promise = async () => { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 4a53a4e0c93..29a568d478b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -164,6 +164,10 @@ export class DebugSession implements IDebugSession { return !!this._options.compact; } + get saveBeforeStart(): boolean { + return this._options.saveBeforeStart ?? !this._options?.parentSession; + } + get compoundRoot(): DebugCompoundRoot | undefined { return this._options.compoundRoot; } @@ -955,7 +959,7 @@ export class DebugSession implements IDebugSession { if (thread) { // Call fetch call stack twice, the first only return the top stack frame. // Second retrieves the rest of the call stack. For performance reasons #25605 - const promises = this.model.fetchCallStack(thread); + const promises = this.model.refreshTopOfCallstack(thread); const focus = async () => { if (focusedThreadDoesNotExist || (!event.body.preserveFocusHint && thread.getCallStack().length)) { const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 1f7614bfee7..3fd1c0238b8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/debugToolBar'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ICommandAction } from 'vs/platform/action/common/action'; +import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -296,7 +296,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl // Debug toolbar const debugViewTitleItems: IDisposable[] = []; -const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => { +const registerDebugToolBarItem = (id: string, title: string | ICommandActionTitle, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a38c878e3bc..7fcf2fa1c08 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -718,7 +718,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { }); } - this.replInput.setDecorations('repl-decoration', DECORATION_KEY, decorations); + this.replInput.setDecorationsByType('repl-decoration', DECORATION_KEY, decorations); } override saveState(): void { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index baac9206484..21dc92d475c 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -206,6 +206,7 @@ export interface IDebugSessionOptions { simple?: boolean; }; startedByUser?: boolean; + saveBeforeStart?: boolean; } export interface IDataBreakpointInfoResponse { @@ -296,6 +297,7 @@ export interface IDebugSession extends ITreeElement { readonly subId: string | undefined; readonly compact: boolean; readonly compoundRoot: DebugCompoundRoot | undefined; + readonly saveBeforeStart: boolean; readonly name: string; readonly isSimpleUI: boolean; readonly autoExpandLazyVariables: boolean; @@ -602,6 +604,8 @@ export interface IDebugModel extends ITreeElement { onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; onDidChangeWatchExpressions: Event; + + fetchCallstack(thread: IThread, levels?: number): Promise; } /** @@ -1086,7 +1090,7 @@ export interface IDebugService { * Returns true if the start debugging was successful. For compound launches, all configurations have to start successfully for it to return success. * On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false. */ - startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart?: boolean): Promise; + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise; /** * Restarts a session or creates a new one if there is no active session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4b20e069cd2..1f50eb36368 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1244,7 +1244,31 @@ export class DebugModel implements IDebugModel { } } - fetchCallStack(thread: Thread): { topCallStack: Promise; wholeCallStack: Promise } { + /** + * Update the call stack and notify the call stack view that changes have occurred. + */ + async fetchCallstack(thread: IThread, levels?: number): Promise { + + if ((thread).reachedEndOfCallStack) { + return; + } + + const totalFrames = thread.stoppedDetails?.totalFrames; + const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined; + + if (!levels || (remainingFrames && levels > remainingFrames)) { + levels = remainingFrames; + } + + if (levels && levels > 0) { + await (thread).fetchCallStack(levels); + this._onDidChangeCallStack.fire(); + } + + return; + } + + refreshTopOfCallstack(thread: Thread): { topCallStack: Promise; wholeCallStack: Promise } { if (thread.session.capabilities.supportsDelayedStackTraceLoading) { // For improved performance load the first stack frame and then load the rest async. let topCallStack = Promise.resolve(); diff --git a/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts b/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts new file mode 100644 index 00000000000..51218a3475d --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/loadedScriptsPicker.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; +import { matchesFuzzy } from 'vs/base/common/filters'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +import { dirname } from 'vs/base/common/resources'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; + + +interface IPickerLoadedScriptItem extends IQuickPickItem { + accept(): void; +} + + +/** + * This function takes a regular quickpick and makes one for loaded scripts that has persistent headers + * e.g. when some picks are filtered out, the ones that are visible still have its header. + */ +export async function showLoadedScriptMenu(accessor: ServicesAccessor) { + const quickInputService = accessor.get(IQuickInputService); + const debugService = accessor.get(IDebugService); + const editorService = accessor.get(IEditorService); + const sessions = debugService.getModel().getSessions(false); + const modelService = accessor.get(IModelService); + const languageService = accessor.get(ILanguageService); + const labelService = accessor.get(ILabelService); + + const localDisposableStore = new DisposableStore(); + const quickPick = quickInputService.createQuickPick(); + localDisposableStore.add(quickPick); + quickPick.matchOnLabel = quickPick.matchOnDescription = quickPick.matchOnDetail = quickPick.sortByLabel = false; + quickPick.placeholder = nls.localize('moveFocusedView.selectView', "Search loaded scripts by name"); + quickPick.items = await _getPicks(quickPick.value, sessions, editorService, modelService, languageService, labelService); + + localDisposableStore.add(quickPick.onDidChangeValue(async () => { + quickPick.items = await _getPicks(quickPick.value, sessions, editorService, modelService, languageService, labelService); + })); + localDisposableStore.add(quickPick.onDidAccept(() => { + const selectedItem = quickPick.selectedItems[0]; + selectedItem.accept(); + quickPick.hide(); + localDisposableStore.dispose(); + })); + quickPick.show(); +} + +async function _getPicksFromSession(session: IDebugSession, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { + const items: Array = []; + items.push({ type: 'separator', label: session.name }); + const sources = await session.getLoadedSources(); + + sources.forEach((element: Source) => { + const pick = _createPick(element, filter, editorService, modelService, languageService, labelService); + if (pick) { + items.push(pick); + } + + }); + return items; +} +async function _getPicks(filter: string, sessions: IDebugSession[], editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): Promise> { + const loadedScriptPicks: Array = []; + + + const picks = await Promise.all( + sessions.map((session) => _getPicksFromSession(session, filter, editorService, modelService, languageService, labelService)) + ); + + for (const row of picks) { + for (const elem of row) { + loadedScriptPicks.push(elem); + } + } + return loadedScriptPicks; +} + +function _createPick(source: Source, filter: string, editorService: IEditorService, modelService: IModelService, languageService: ILanguageService, labelService: ILabelService): IPickerLoadedScriptItem | undefined { + + const label = labelService.getUriBasenameLabel(source.uri); + const desc = labelService.getUriLabel(dirname(source.uri)); + + // manually filter so that headers don't get filtered out + const labelHighlights = matchesFuzzy(filter, label, true); + const descHighlights = matchesFuzzy(filter, desc, true); + if (labelHighlights || descHighlights) { + return { + label, + description: desc === '.' ? undefined : desc, + highlights: { label: labelHighlights ?? undefined, description: descHighlights ?? undefined }, + iconClasses: getIconClasses(modelService, languageService, source.uri), + accept: () => { + if (source.available) { + source.openInEditor(editorService, { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 }); + } + } + }; + } + return undefined; +} diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts index ab6d37993ca..601ff4f9b1e 100644 --- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts +++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); +const appender = new OneDataSystemAppender(undefined, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); process.once('exit', () => appender.flush()); const channel = new TelemetryAppenderChannel([appender]); diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index 1bebaa31adb..1596e346e31 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -190,6 +190,10 @@ export class MockSession implements IDebugSession { return undefined; } + get saveBeforeStart(): boolean { + return true; + } + get isSimpleUI(): boolean { return false; } diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts similarity index 77% rename from src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts rename to src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 3c102115db8..deb0cb486bc 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -7,10 +7,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_TITLE, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -19,13 +19,12 @@ import { joinPath, relativePath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { SessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService'; +import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -39,34 +38,33 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua import { Schemas } from 'vs/base/common/network'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService'; -registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); +registerSingleton(IEditSessionsLogService, EditSessionsLogService); +registerSingleton(IEditSessionsWorkbenchService, EditSessionsWorkbenchService); -const resumeLatestCommand = { - id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), -}; -const storeCurrentCommand = { - id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), -}; -const continueEditSessionCommand = { +const continueEditSessionCommand: IAction2Options = { id: '_workbench.experimental.editSessions.actions.continueEditSession', - title: localize('continue edit session', "Continue Edit Session..."), + title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true }; -const openLocalFolderCommand = { +const openLocalFolderCommand: IAction2Options = { id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', - title: localize('continue edit session in local folder', "Open In Local Folder"), + title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, + category: EDIT_SESSION_SYNC_CATEGORY, + precondition: IsWebContext }; const queryParamName = 'editSessionId'; +const experimentalSettingName = 'workbench.experimental.editSessions.enabled'; -export class SessionSyncContribution extends Disposable implements IWorkbenchContribution { +export class EditSessionsContribution extends Disposable implements IWorkbenchContribution { private registered = false; private continueEditSessionOptions: ContinueEditSessionItem[] = []; constructor( - @ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService, + @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService, @IFileService private readonly fileService: IFileService, @IProgressService private readonly progressService: IProgressService, @IOpenerService private readonly openerService: IOpenerService, @@ -74,7 +72,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon @ISCMService private readonly scmService: ISCMService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, - @ILogService private readonly logService: ILogService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IProductService private readonly productService: IProductService, @IConfigurationService private configurationService: IConfigurationService, @@ -91,7 +89,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('workbench.experimental.editSessions.enabled')) { + if (e.affectsConfiguration(experimentalSettingName)) { this.registerActions(); } }); @@ -127,7 +125,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } private registerActions() { - if (this.registered || this.configurationService.getValue('workbench.experimental.editSessions.enabled') !== true) { + if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) { + this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`); return; } @@ -145,11 +144,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const that = this; this._register(registerAction2(class ContinueEditSessionAction extends Action2 { constructor() { - super({ - id: continueEditSessionCommand.id, - title: continueEditSessionCommand.title, - f1: true - }); + super(continueEditSessionCommand); } async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise { @@ -157,7 +152,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon if (uri === undefined) { return; } // Run the store action to get back a ref - const ref = await that.storeEditSession(); + const ref = await that.storeEditSession(false); // Append the ref to the URI if (ref !== undefined) { @@ -166,11 +161,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}` }); } else { - that.logService.warn(`Edit Sessions: Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); + that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); } // Open the URI - that.logService.info(`Edit Sessions: opening ${uri.toString()}`); + that.logService.info(`Opening ${uri.toString()}`); await that.openerService.open(uri, { openExternal: true }); } })); @@ -181,11 +176,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { constructor() { super({ - id: resumeLatestCommand.id, - title: resumeLatestCommand.title, - menu: { - id: MenuId.CommandPalette, - } + id: 'workbench.experimental.editSessions.actions.resumeLatest', + title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, }); } @@ -203,11 +197,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { constructor() { super({ - id: storeCurrentCommand.id, - title: storeCurrentCommand.title, - menu: { - id: MenuId.CommandPalette, - } + id: 'workbench.experimental.editSessions.actions.storeCurrent', + title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, }); } @@ -215,19 +208,24 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon await that.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('storing edit session', 'Storing edit session...') - }, async () => await that.storeEditSession()); + }, async () => await that.storeEditSession(true)); } })); } async applyEditSession(ref?: string): Promise { if (ref !== undefined) { - this.logService.info(`Edit Sessions: Applying edit session with ref ${ref}.`); + this.logService.info(`Applying edit session with ref ${ref}.`); } - const data = await this.sessionSyncWorkbenchService.read(ref); + const data = await this.editSessionsWorkbenchService.read(ref); if (!data) { - this.logService.info(`Edit Sessions: Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); + if (ref === undefined) { + this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); + } else { + this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); + } + this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); return; } const editSession = data.editSession; @@ -245,7 +243,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon for (const folder of editSession.folders) { const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name); if (!folderRoot) { - this.logService.info(`Edit Sessions: Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); + this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); continue; } @@ -270,7 +268,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const result = await this.dialogService.confirm({ message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), type: 'warning', - title: EDIT_SESSION_SYNC_TITLE + title: EDIT_SESSION_SYNC_CATEGORY.value }); if (!result.confirmed) { return; @@ -285,16 +283,18 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } - this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace.`); - await this.sessionSyncWorkbenchService.delete(ref); + this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); + await this.editSessionsWorkbenchService.delete(ref); + this.logService.info(`Deleted edit session with ref ${ref}.`); } catch (ex) { - this.logService.error('Edit Sessions: Failed to apply edit session, reason: ', (ex as Error).toString()); + this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); } } - async storeEditSession(): Promise { + async storeEditSession(fromStoreCommand: boolean): Promise { const folders: Folder[] = []; + let hasEdits = false; for (const repository of this.scmService.repositories) { // Look through all resource groups and compute which files were added/modified/deleted @@ -306,7 +306,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon for (const uri of trackedUris) { const workspaceFolder = this.contextService.getWorkspaceFolder(uri); if (!workspaceFolder) { - this.logService.info(`Edit Sessions: Skipping working change ${uri.toString()} as no associated workspace folder was found.`); + this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`); continue; } @@ -321,6 +321,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } catch { } + hasEdits = true; + if (await this.fileService.exists(uri)) { workingChanges.push({ type: ChangeType.Addition, fileType: FileType.File, contents: (await this.fileService.readFile(uri)).value.toString(), relativeFilePath: relativeFilePath }); } else { @@ -332,15 +334,23 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon folders.push({ workingChanges, name: name ?? '' }); } + if (!hasEdits) { + this.logService.info('Skipping storing edit session as there are no edits to store.'); + if (fromStoreCommand) { + this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.')); + } + return undefined; + } + const data: EditSession = { folders, version: 1 }; try { - this.logService.info(`Edit Sessions: Storing edit session...`); - const ref = await this.sessionSyncWorkbenchService.write(data); - this.logService.info(`Edit Sessions: Stored edit session with ref ${ref}.`); + this.logService.info(`Storing edit session...`); + const ref = await this.editSessionsWorkbenchService.write(data); + this.logService.info(`Stored edit session with ref ${ref}.`); return ref; } catch (ex) { - this.logService.error(`Edit Sessions: Failed to store edit session, reason: `, (ex as Error).toString()); + this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString()); type UploadFailedEvent = { reason: string }; type UploadFailedClassification = { @@ -352,11 +362,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon switch (ex.code) { case UserDataSyncErrorCode.TooLarge: // Uploading a payload can fail due to server size limits - this.telemetryService.publicLog2('sessionSync.upload.failed', { reason: 'TooLarge' }); + this.telemetryService.publicLog2('editSessions.upload.failed', { reason: 'TooLarge' }); this.notificationService.error(localize('payload too large', 'Your edit session exceeds the size limit and cannot be stored.')); break; default: - this.telemetryService.publicLog2('sessionSync.upload.failed', { reason: 'unknown' }); + this.telemetryService.publicLog2('editSessions.upload.failed', { reason: 'unknown' }); this.notificationService.error(localize('payload failed', 'Your edit session cannot be stored.')); break; } @@ -381,11 +391,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const that = this; this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { constructor() { - super({ - id: openLocalFolderCommand.id, - title: openLocalFolderCommand.title, - precondition: IsWebContext - }); + super(openLocalFolderCommand); } async run(accessor: ServicesAccessor): Promise { @@ -496,7 +502,7 @@ const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(SessionSyncContribution, LifecyclePhase.Restored); +workbenchRegistry.registerWorkbenchContribution(EditSessionsContribution, LifecyclePhase.Restored); Registry.as(Extensions.Configuration).registerConfiguration({ ...workbenchConfigurationNodeBase, diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts similarity index 85% rename from src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts rename to src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index f2c7a284c62..af25b5ee15a 100644 --- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -10,7 +10,6 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -19,20 +18,20 @@ import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDat import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_TITLE, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; -export class SessionSyncWorkbenchService extends Disposable implements ISessionSyncWorkbenchService { +export class EditSessionsWorkbenchService extends Disposable implements IEditSessionsWorkbenchService { _serviceBrand = undefined; - private serverConfiguration = this.productService['sessionSync.store']; + private serverConfiguration = this.productService['editSessions.store']; private storeClient: UserDataSyncStoreClient | undefined; #authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined; - private static CACHED_SESSION_STORAGE_KEY = 'editSessionSyncAccountPreference'; + private static CACHED_SESSION_STORAGE_KEY = 'editSessionAccountPreference'; private initialized = false; private readonly signedInContext: IContextKey; @@ -44,7 +43,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IExtensionService private readonly extensionService: IExtensionService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILogService private readonly logService: ILogService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, @IProductService private readonly productService: IProductService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IRequestService private readonly requestService: IRequestService, @@ -120,6 +119,9 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } private async initialize() { + if (this.initialized) { + return; + } this.initialized = await this.doInitialize(); this.signedInContext.set(this.initialized); } @@ -140,7 +142,10 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS if (!this.storeClient) { this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); - this._register(this.storeClient.onTokenFailed(() => this.clearAuthenticationPreference())); + this._register(this.storeClient.onTokenFailed(() => { + this.logService.info('Clearing edit sessions authentication preference because of successive token failures.'); + this.clearAuthenticationPreference(); + })); } // If we already have an existing auth session in memory, use that @@ -149,9 +154,12 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } // If the user signed in previously and the session is still available, reuse that without prompting the user again - if (this.existingSessionId) { + const existingSessionId = this.existingSessionId; + if (existingSessionId) { + this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`); const existing = await this.getExistingSession(); if (existing !== undefined) { + this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); return true; @@ -164,6 +172,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); this.existingSessionId = session.id; + this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); return true; } @@ -277,14 +286,14 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } private get existingSessionId() { - return this.storageService.get(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + return this.storageService.get(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); } private set existingSessionId(sessionId: string | undefined) { if (sessionId === undefined) { - this.storageService.remove(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + this.storageService.remove(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); } else { - this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.USER); + this.storageService.store(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); } } @@ -294,12 +303,17 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS } private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise { - if (e.key === SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY + if (e.key === EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY && e.scope === StorageScope.APPLICATION - && this.#authenticationInfo?.sessionId !== this.existingSessionId ) { - this.#authenticationInfo = undefined; - this.initialized = false; + const newSessionId = this.existingSessionId; + const previousSessionId = this.#authenticationInfo?.sessionId; + + if (previousSessionId !== newSessionId) { + this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`); + this.#authenticationInfo = undefined; + this.initialized = false; + } } } @@ -321,8 +335,9 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 { constructor() { super({ - id: 'workbench.sessionSync.actions.resetAuth', - title: localize('reset auth', '{0}: Sign Out', EDIT_SESSION_SYNC_TITLE), + id: 'workbench.editSessions.actions.resetAuth', + title: localize('reset auth', 'Sign Out'), + category: EDIT_SESSION_SYNC_CATEGORY, precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), menu: [{ id: MenuId.CommandPalette, diff --git a/src/vs/workbench/services/sessionSync/common/sessionSync.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts similarity index 70% rename from src/vs/workbench/services/sessionSync/common/sessionSync.ts rename to src/vs/workbench/contrib/editSessions/common/editSessions.ts index 6eafdb65490..789976a50e5 100644 --- a/src/vs/workbench/services/sessionSync/common/sessionSync.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -4,13 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; -export const EDIT_SESSION_SYNC_TITLE = localize('session sync', 'Edit Sessions'); +export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { + original: 'Edit Sessions', + value: localize('session sync', 'Edit Sessions') +}; -export const ISessionSyncWorkbenchService = createDecorator('ISessionSyncWorkbenchService'); -export interface ISessionSyncWorkbenchService { +export const IEditSessionsWorkbenchService = createDecorator('IEditSessionsWorkbenchService'); +export interface IEditSessionsWorkbenchService { _serviceBrand: undefined; read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>; @@ -18,6 +23,9 @@ export interface ISessionSyncWorkbenchService { delete(ref: string): Promise; } +export const IEditSessionsLogService = createDecorator('IEditSessionsLogService'); +export interface IEditSessionsLogService extends ILogService { } + export enum ChangeType { Addition = 1, Deletion = 2, diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts new file mode 100644 index 00000000000..a18c9e29304 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; + +export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService { + + declare readonly _serviceBrand: undefined; + private readonly logger: ILogger; + + constructor( + @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + super(); + this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' })); + } + + trace(message: string, ...args: any[]): void { + this.logger.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + this.logger.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + this.logger.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + this.logger.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + this.logger.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + this.logger.critical(message, ...args); + } + + flush(): void { + this.logger.flush(); + } +} diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts similarity index 75% rename from src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts rename to src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index cdb412c5060..b7caca6f077 100644 --- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -9,8 +9,8 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { NullLogService, ILogService } from 'vs/platform/log/common/log'; -import { SessionSyncContribution } from 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { EditSessionsContribution } from 'vs/workbench/contrib/editSessions/browser/editSessions.contribution'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ISCMService } from 'vs/workbench/contrib/scm/common/scm'; @@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { mock } from 'vs/base/test/common/mock'; import * as sinon from 'sinon'; import * as assert from 'assert'; -import { ChangeType, FileType, ISessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { ChangeType, FileType, IEditSessionsLogService, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -34,12 +34,15 @@ const folderUri = URI.file(`/${folderName}`); suite('Edit session sync', () => { let instantiationService: TestInstantiationService; - let sessionSyncContribution: SessionSyncContribution; + let editSessionsContribution: EditSessionsContribution; let fileService: FileService; + let sandbox: sinon.SinonSandbox; const disposables = new DisposableStore(); - setup(() => { + suiteSetup(() => { + sandbox = sinon.createSandbox(); + instantiationService = new TestInstantiationService(); // Set up filesystem @@ -49,14 +52,14 @@ suite('Edit session sync', () => { fileService.registerProvider(Schemas.file, fileSystemProvider); // Stub out all services - instantiationService.stub(ILogService, logService); + instantiationService.stub(IEditSessionsLogService, logService); instantiationService.stub(IFileService, fileService); instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock() { }); + instantiationService.stub(IEditSessionsWorkbenchService, new class extends mock() { }); instantiationService.stub(IProgressService, ProgressService); instantiationService.stub(ISCMService, SCMService); instantiationService.stub(IEnvironmentService, TestEnvironmentService); - instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { sessionSync: { enabled: true } } } })); + instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { editSessions: { enabled: true } } } })); instantiationService.stub(IWorkspaceContextService, new class extends mock() { override getWorkspace() { return { @@ -71,7 +74,10 @@ suite('Edit session sync', () => { } }); - sessionSyncContribution = instantiationService.createInstance(SessionSyncContribution); + // Stub repositories + instantiationService.stub(ISCMService, '_repositories', new Map()); + + editSessionsContribution = instantiationService.createInstance(EditSessionsContribution); }); teardown(() => { @@ -100,20 +106,29 @@ suite('Edit session sync', () => { }; // Stub sync service to return edit session data - const sandbox = sinon.createSandbox(); const readStub = sandbox.stub().returns({ editSession, ref: '0' }); - instantiationService.stub(ISessionSyncWorkbenchService, 'read', readStub); - - // Stub repositories - instantiationService.stub(ISCMService, '_repositories', new Map()); + instantiationService.stub(IEditSessionsWorkbenchService, 'read', readStub); // Create root folder await fileService.createFolder(folderUri); // Apply edit session - await sessionSyncContribution.applyEditSession(); + await editSessionsContribution.applyEditSession(); // Verify edit session was correctly applied assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); }); + + test('Edit session not stored if there are no edits', async function () { + const writeStub = sandbox.stub(); + instantiationService.stub(IEditSessionsWorkbenchService, 'write', writeStub); + + // Create root folder + await fileService.createFolder(folderUri); + + await editSessionsContribution.storeEditSession(true); + + // Verify that we did not attempt to write the edit session + assert.equal(writeStub.called, false); + }); }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 6fbc52d18ba..b3c51e8a568 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -75,7 +75,9 @@ import { Event } from 'vs/base/common/event'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/extensions/browser/unsupportedExtensionsMigrationContribution'; import { isWeb } from 'vs/base/common/platform'; -import { ExtensionsCleaner } from 'vs/workbench/contrib/extensions/browser/extensionsCleaner'; +import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import product from 'vs/platform/product/common/product'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -227,7 +229,7 @@ Registry.as(ConfigurationExtensions.Configuration) 'extensions.experimental.useUtilityProcess': { type: 'boolean', description: localize('extensionsUseUtilityProcess', "When enabled, the extension host will be launched using the new UtilityProcess Electron API."), - default: true + default: product.quality === 'stable' ? false : true // disabled by default in stable for now }, [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { type: 'object', @@ -1559,6 +1561,16 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } +class ExtensionStorageCleaner implements IWorkbenchContribution { + + constructor( + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IStorageService storageService: IStorageService, + ) { + ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); + } +} + const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); @@ -1571,7 +1583,7 @@ workbenchRegistry.registerWorkbenchContribution(ExtensionEnablementWorkspaceTrus workbenchRegistry.registerWorkbenchContribution(ExtensionsCompletionItemsProvider, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(UnsupportedExtensionsMigrationContrib, LifecyclePhase.Eventually); if (isWeb) { - workbenchRegistry.registerWorkbenchContribution(ExtensionsCleaner, LifecyclePhase.Eventually); + workbenchRegistry.registerWorkbenchContribution(ExtensionStorageCleaner, LifecyclePhase.Eventually); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 710e811a4f7..c6d34dc012f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1623,38 +1623,38 @@ export class SetColorThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; - private colorThemes: IWorkbenchColorTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); - workbenchThemeService.getColorThemes().then(colorThemes => { - this.colorThemes = colorThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.colorThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; + this.workbenchThemeService.getColorThemes().then(colorThemes => { + this.enabled = this.computeEnablement(colorThemes); + this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; + }); + } + + private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.colorThemes = await this.workbenchThemeService.getColorThemes(); + const colorThemes = await this.workbenchThemeService.getColorThemes(); - this.update(); - if (!this.enabled) { + if (!this.computeEnablement(colorThemes)) { return; } const currentTheme = this.workbenchThemeService.getColorTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.colorThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1674,37 +1674,37 @@ export class SetFileIconThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - private fileIconThemes: IWorkbenchFileIconTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); - workbenchThemeService.getFileIconThemes().then(fileIconThemes => { - this.fileIconThemes = fileIconThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.fileIconThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => { + this.enabled = this.computeEnablement(fileIconThemes); + this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); - this.update(); - if (!this.enabled) { + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + if (!this.computeEnablement(fileIconThemes)) { return; } const currentTheme = this.workbenchThemeService.getFileIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.fileIconThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1724,38 +1724,38 @@ export class SetProductIconThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; - private productIconThemes: IWorkbenchProductIconTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this)); - workbenchThemeService.getProductIconThemes().then(productIconThemes => { - this.productIconThemes = productIconThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.productIconThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + this.workbenchThemeService.getProductIconThemes().then(productIconThemes => { + this.enabled = this.computeEnablement(productIconThemes); + this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.productIconThemes = await this.workbenchThemeService.getProductIconThemes(); - this.update(); - if (!this.enabled) { + const productIconThemes = await this.workbenchThemeService.getProductIconThemes(); + if (!this.computeEnablement(productIconThemes)) { return; } const currentTheme = this.workbenchThemeService.getProductIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.productIconThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts b/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts deleted file mode 100644 index 2d9b7872491..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/extensionsCleaner.ts +++ /dev/null @@ -1,19 +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 { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; - -export class ExtensionsCleaner implements IWorkbenchContribution { - - constructor( - @IExtensionManagementService extensionManagementService: IExtensionManagementService, - @IStorageService storageService: IStorageService, - ) { - ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); - } -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index ae5a743a290..31cc538fa56 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -21,7 +21,7 @@ import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAct import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -70,6 +70,7 @@ const HasInstalledExtensionsContext = new RawContextKey('hasInstalledEx const BuiltInExtensionsContext = new RawContextKey('builtInExtensions', false); const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); const SearchUnsupportedWorkspaceExtensionsContext = new RawContextKey('searchUnsupportedWorkspaceExtensions', false); +const SearchDeprecatedExtensionsContext = new RawContextKey('searchDeprecatedExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { @@ -104,6 +105,9 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio /* Trust Required extensions views */ viewDescriptors.push(...this.createUnsupportedWorkspaceExtensionsViewDescriptors()); + /* Other Local Filtered extensions views */ + viewDescriptors.push(...this.createOtherLocalFilteredExtensionsViewDescriptors()); + Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container); } @@ -154,7 +158,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio constructor() { super({ id: 'workbench.extensions.installLocalExtensions', - get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); }, + get title() { + return { + value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label), + original: `Install Local Extensions in '${server.label}'...`, + }; + }, category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), icon: installLocalInRemoteIcon, f1: true, @@ -264,7 +273,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.marketplace', name: localize('marketPlace', "Marketplace"), - ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), + ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), }); @@ -414,6 +423,19 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return viewDescriptors; } + private createOtherLocalFilteredExtensionsViewDescriptors(): IViewDescriptor[] { + const viewDescriptors: IViewDescriptor[] = []; + + viewDescriptors.push({ + id: 'workbench.views.extensions.deprecatedExtensions', + name: localize('deprecated', "Deprecated"), + ctorDescriptor: new SyncDescriptor(DeprecatedExtensionsView, [{}]), + when: ContextKeyExpr.and(SearchDeprecatedExtensionsContext), + }); + + return viewDescriptors; + } + } export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { @@ -429,6 +451,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private builtInExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; private searchWorkspaceUnsupportedExtensionsContextKey: IContextKey; + private searchDeprecatedExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; @@ -465,6 +488,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService); this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService); this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService); + this.searchDeprecatedExtensionsContextKey = SearchDeprecatedExtensionsContext.bindTo(contextKeyService); this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService); this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService); this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService); @@ -473,6 +497,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService); this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); this._register(this.paneCompositeService.onDidPaneCompositeOpen(e => { if (e.viewContainerLocation === ViewContainerLocation.Sidebar) { this.onViewletOpen(e.composite); } }, this)); + this._register(extensionsWorkbenchService.onReset(() => this.refresh())); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE, StorageTarget.USER); } @@ -634,6 +659,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value)); this.searchWorkspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(value)); + this.searchDeprecatedExtensionsContextKey.set(ExtensionsListView.isSearchDeprecatedExtensionsQuery(value)); this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 01dd09f76fa..1ed384b4e22 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -39,7 +39,7 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction, Action, Separator, ActionRunner } from 'vs/base/common/actions'; import { ExtensionIdentifier, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -176,7 +176,12 @@ export class ExtensionsListView extends ViewPane { horizontalScrolling: false, accessibilityProvider: >{ getAriaLabel(extension: IExtension | null): string { - return extension ? localize('extension.arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description) : ''; + if (!extension) { + return ''; + } + const publisher = localize('extension.arialabel.publihser', "Publisher {0}", extension.publisherDisplayName); + const deprecated = extension?.deprecationInfo ? localize('extension.arialabel.deprecated', "Deprecated") : ''; + return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description}`; }, getWidgetAriaLabel(): string { return localize('extensions', "Extensions"); @@ -345,7 +350,7 @@ export class ExtensionsListView extends ViewPane { private async queryLocal(query: Query, options: IQueryOptions): Promise { const local = await this.extensionsWorkbenchService.queryLocal(this.options.server); const runningExtensions = await this.extensionService.getExtensions(); - let { extensions, canIncludeInstalledExtensions } = this.filterLocal(local, runningExtensions, query, options); + let { extensions, canIncludeInstalledExtensions } = await this.filterLocal(local, runningExtensions, query, options); const disposables = new DisposableStore(); const onDidChangeModel = disposables.add(new Emitter>()); @@ -358,7 +363,7 @@ export class ExtensionsListView extends ViewPane { ), () => undefined)(async () => { const local = this.options.server ? this.extensionsWorkbenchService.installed.filter(e => e.server === this.options.server) : this.extensionsWorkbenchService.local; const runningExtensions = await this.extensionService.getExtensions(); - const { extensions: newExtensions } = this.filterLocal(local, runningExtensions, query, options); + const { extensions: newExtensions } = await this.filterLocal(local, runningExtensions, query, options); if (!isDisposed) { const mergedExtensions = this.mergeAddedExtensions(extensions, newExtensions); if (mergedExtensions) { @@ -376,7 +381,7 @@ export class ExtensionsListView extends ViewPane { }; } - private filterLocal(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): { extensions: IExtension[]; canIncludeInstalledExtensions: boolean } { + private async filterLocal(local: IExtension[], runningExtensions: IExtensionDescription[], query: Query, options: IQueryOptions): Promise<{ extensions: IExtension[]; canIncludeInstalledExtensions: boolean }> { const value = query.value; let extensions: IExtension[] = []; let canIncludeInstalledExtensions = true; @@ -406,6 +411,10 @@ export class ExtensionsListView extends ViewPane { extensions = this.filterWorkspaceUnsupportedExtensions(local, query, options); } + else if (/@deprecated/i.test(query.value)) { + extensions = await this.filterDeprecatedExtensions(local, query, options); + } + return { extensions, canIncludeInstalledExtensions }; } @@ -616,6 +625,13 @@ export class ExtensionsListView extends ViewPane { return this.sortExtensions(local, options); } + private async filterDeprecatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): Promise { + const value = query.value.replace(/@deprecated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); + const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); + const deprecatedExtensionIds = Object.keys(extensionsControlManifest.deprecated); + local = local.filter(e => deprecatedExtensionIds.includes(e.identifier.id) && (!value || e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1)); + return this.sortExtensions(local, options); + } private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined { const oldExtensions = [...extensions]; @@ -653,10 +669,6 @@ export class ExtensionsListView extends ViewPane { return this.queryRecommendations(query, options, token); } - if (/@deprecated/i.test(query.value)) { - return this.getDeprecatedExtensions(options, token); - } - if (/\bcurated:([^\s]+)\b/.test(query.value)) { return this.getCuratedModel(query, options, token); } @@ -752,16 +764,6 @@ export class ExtensionsListView extends ViewPane { return new PagedModel([]); } - private async getDeprecatedExtensions(options: IQueryOptions, token: CancellationToken): Promise> { - const extensionsControlManifest = await this.extensionManagementService.getExtensionsControlManifest(); - const deprecatedExtensionIds = Object.keys(extensionsControlManifest.deprecated); - if (deprecatedExtensionIds.length) { - const pager = await this.extensionsWorkbenchService.queryGallery({ ...options, names: deprecatedExtensionIds, text: undefined }, token); - return this.getPagedModel(pager); - } - return this.getPagedModel([]); - } - private isRecommendationsQuery(query: Query): boolean { return ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value) || ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value) @@ -1024,6 +1026,7 @@ export class ExtensionsListView extends ViewPane { || this.isBuiltInExtensionsQuery(query) || this.isSearchBuiltInExtensionsQuery(query) || this.isBuiltInGroupExtensionsQuery(query) + || this.isSearchDeprecatedExtensionsQuery(query) || this.isSearchWorkspaceUnsupportedExtensionsQuery(query); } @@ -1059,6 +1062,10 @@ export class ExtensionsListView extends ViewPane { return /@disabled/i.test(query); } + static isSearchDeprecatedExtensionsQuery(query: string): boolean { + return /@deprecated\s?.*/i.test(query); + } + static isRecommendedExtensionsQuery(query: string): boolean { return /^@recommended$/i.test(query.trim()); } @@ -1194,6 +1201,30 @@ export class VirtualWorkspacePartiallySupportedExtensionsView extends Extensions } } +export class DeprecatedExtensionsView extends ExtensionsListView { + override async show(query: string): Promise> { + return ExtensionsListView.isSearchDeprecatedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); + } +} + +export class SearchMarketplaceExtensionsView extends ExtensionsListView { + + private readonly reportSearchFinishedDelayer = this._register(new ThrottledDelayer(2000)); + private searchWaitPromise: Promise = Promise.resolve(); + + override async show(query: string): Promise> { + const queryPromise = super.show(query); + this.reportSearchFinishedDelayer.trigger(() => this.reportSearchFinished()); + this.searchWaitPromise = queryPromise.then(null, null); + return queryPromise; + } + + private async reportSearchFinished(): Promise { + await this.searchWaitPromise; + this.telemetryService.publicLog2('extensionsView:MarketplaceSearchFinished'); + } +} + export class DefaultRecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended:all'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index e23dee80a2c..a533f0ad244 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -32,7 +32,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, TargetPlatform, ExtensionIdentifier, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -416,8 +416,11 @@ class Extensions extends Disposable { extension.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; } - private readonly _onChange: Emitter<{ extension: Extension; operation?: InstallOperation } | undefined> = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); - get onChange(): Event<{ extension: Extension; operation?: InstallOperation } | undefined> { return this._onChange.event; } + private readonly _onChange = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); + get onChange() { return this._onChange.event; } + + private readonly _onReset = this._register(new Emitter()); + get onReset() { return this._onReset.event; } private installing: Extension[] = []; private uninstalling: Extension[] = []; @@ -435,6 +438,7 @@ class Extensions extends Disposable { this._register(server.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e.identifier))); this._register(server.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); + this._register(server.extensionManagementService.onDidChangeProfileExtensions(e => this.onDidChangeProfileExtensions(e.added, e.removed))); this._register(extensionEnablementService.onEnablementChanged(e => this.onEnablementChanged(e))); } @@ -554,6 +558,23 @@ class Extensions extends Disposable { } } + private async onDidChangeProfileExtensions(added: ILocalExtension[], removed: ILocalExtension[]): Promise { + const extensionsControlManifest = await this.server.extensionManagementService.getExtensionsControlManifest(); + for (const addedExtension of added) { + if (this.installed.find(e => areSameExtensions(e.identifier, addedExtension.identifier))) { + const extension = this.instantiationService.createInstance(Extension, this.stateProvider, this.server, addedExtension, undefined); + this.installed.push(extension); + Extensions.updateExtensionFromControlManifest(extension, extensionsControlManifest); + } + } + + if (removed.length) { + this.installed = this.installed.filter(e => !removed.some(removedExtension => areSameExtensions(e.identifier, removedExtension.identifier))); + } + + this._onReset.fire(); + } + private async onDidInstallExtensions(results: readonly InstallExtensionResult[]): Promise { for (const event of results) { const { local, source } = event; @@ -660,6 +681,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } + private readonly _onReset = new Emitter(); + get onReset() { return this._onReset.event; } + readonly preferPreReleases = this.productService.quality !== 'stable'; private installing: IExtension[] = []; @@ -696,14 +720,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (extensionManagementServerService.localExtensionManagementServer) { this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); + this._register(this.localExtensions.onReset(e => { this._onChange.fire(undefined); this._onReset.fire(); })); } if (extensionManagementServerService.remoteExtensionManagementServer) { this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); + this._register(this.remoteExtensions.onReset(e => { this._onChange.fire(undefined); this._onReset.fire(); })); } if (extensionManagementServerService.webExtensionManagementServer) { this.webExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.webExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.webExtensions.onChange(e => this._onChange.fire(e ? e.extension : undefined))); + this._register(this.webExtensions.onReset(e => { this._onChange.fire(undefined); this._onReset.fire(); })); } this.updatesCheckDelayer = new ThrottledDelayer(ExtensionsWorkbenchService.UpdatesCheckInterval); @@ -749,6 +776,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.updateContexts(); this.updateActivity(); })); + + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); } private _reportTelemetry() { const extensionIds = this.installed.filter(extension => @@ -1568,6 +1597,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }).then(undefined, error => this.onError(error)); } + //#region Ignore Autoupdates when specific versions are installed + /* TODO: @sandy081 Extension version shall be moved to extensions.json file */ private _ignoredAutoUpdateExtensions: string[] | undefined; private get ignoredAutoUpdateExtensions(): string[] { @@ -1582,6 +1613,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.storageService.store('extensions.ignoredAutoUpdateExtension', JSON.stringify(this._ignoredAutoUpdateExtensions), StorageScope.PROFILE, StorageTarget.MACHINE); } + private onDidChangeStorage(e: IStorageValueChangeEvent): void { + if (e.scope === StorageScope.PROFILE && e.key === 'extensions.ignoredAutoUpdateExtension') { + this._ignoredAutoUpdateExtensions = undefined; + } + } + private ignoreAutoUpdate(extensionKey: ExtensionKey): void { if (!this.isAutoUpdateIgnored(extensionKey)) { this.ignoredAutoUpdateExtensions = [...this.ignoredAutoUpdateExtensions, extensionKey.toString()]; @@ -1596,4 +1633,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.ignoredAutoUpdateExtensions = this.ignoredAutoUpdateExtensions.filter(extensionId => this.local.some(local => !!local.local && new ExtensionKey(local.identifier, local.version).toString() === extensionId)); } + //#endregion + } diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index d1b91197ff4..57b4387c1c7 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -14,7 +14,7 @@ import { localize } from 'vs/nls'; import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { IProductService } from 'vs/platform/product/common/productService'; import { ImportantExtensionTip } from 'vs/base/common/product'; -import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { basename, extname } from 'vs/base/common/resources'; @@ -115,10 +115,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations { this.tasExperimentService = tasExperimentService; if (productService.extensionTips) { - forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value)); + Object.entries(productService.extensionTips).forEach(([key, value]) => this.extensionTips.set(key.toLowerCase(), value)); } if (productService.extensionImportantTips) { - forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips.set(key.toLowerCase(), value)); + Object.entries(productService.extensionImportantTips).forEach(([key, value]) => this.importantExtensionTips.set(key.toLowerCase(), value)); } } @@ -153,7 +153,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const cachedRecommendations = this.getCachedRecommendations(); const now = Date.now(); // Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore - forEach(cachedRecommendations, ({ key, value }) => { + Object.entries(cachedRecommendations).forEach(([key, value]) => { const diff = (now - value) / milliSecondsInADay; if (diff <= 7 && allRecommendations.indexOf(key) > -1) { this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value }); @@ -400,7 +400,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, >{}); } const result: IStringDictionary = {}; - forEach(storedRecommendations, ({ key, value }) => { + Object.entries(storedRecommendations).forEach(([key, value]) => { if (typeof value === 'number') { result[key.toLowerCase()] = value; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 4065b9d8ddf..60c5b415c54 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -91,6 +91,7 @@ export const IExtensionsWorkbenchService = createDecorator; + readonly onReset: Event; readonly preferPreReleases: boolean; readonly local: IExtension[]; readonly installed: IExtension[]; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 2641877c8cd..67ab692b603 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -628,21 +628,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ args: [ { isOptional: true, - name: 'viewType', - description: 'The editor view type', + name: 'New Untitled File args', + description: 'The editor view type and language ID if known', schema: { 'type': 'object', - 'required': ['viewType'], 'properties': { 'viewType': { 'type': 'string' + }, + 'languageId': { + 'type': 'string' } } } } ] }, - handler: async (accessor, args?: { viewType: string }) => { + handler: async (accessor, args?: { languageId?: string; viewType?: string }) => { const editorService = accessor.get(IEditorService); await editorService.openEditor({ @@ -650,7 +652,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ options: { override: args?.viewType, pinned: true - } + }, + languageId: args?.languageId, }); } }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 592d559451a..0db2db173e1 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -185,7 +185,7 @@ configurationRegistry.registerConfiguration({ 'files.autoGuessEncoding': { 'type': 'boolean', 'default': false, - 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."), + 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.eol': { @@ -475,7 +475,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.excludeGitIgnore': { type: 'boolean', - markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."), + markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to {0}.", '`#files.exclude#`'), default: false, scope: ConfigurationScope.RESOURCE }, @@ -487,7 +487,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.fileNesting.expand': { 'type': 'boolean', - 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."), + 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. {0} must be set for this to take effect.", '`#explorer.fileNesting.enabled#`'), 'default': true, }, 'explorer.fileNesting.patterns': { diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 2766516fdc0..e3d83192f51 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -174,7 +174,10 @@ registerAction2(class StartReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.startReadingLineWithHint', - title: localize('read.title', 'Read Line With Inline Hints'), + title: { + value: localize('read.title', 'Read Line With Inline Hints'), + original: 'Read Line With Inline Hints' + }, precondition: EditorContextKeys.hasInlayHintsProvider, f1: true }); @@ -193,7 +196,10 @@ registerAction2(class StopReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.stopReadingLineWithHint', - title: localize('stop.title', 'Stop Inlay Hints Reading'), + title: { + value: localize('stop.title', 'Stop Inlay Hints Reading'), + original: 'Stop Inlay Hints Reading' + }, precondition: InlayHintsAccessibility.IsReading, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 8b70cab1d33..c49ec5ad519 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -9,6 +9,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; +import { extname } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -25,12 +26,13 @@ import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { contrastBorder, listInactiveSelectionBackground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -47,9 +49,10 @@ import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/inte import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -213,12 +216,26 @@ export class InteractiveDocumentContribution extends Disposable implements IWork priority: RegisteredEditorPriority.exclusive }, { - canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive, + canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'), singlePerResource: true }, - ({ resource }) => { - const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === resource.toString()); - return editorInput!; + ({ resource, options }) => { + const data = CellUri.parse(resource); + let notebookUri: URI = resource; + let cellOptions: IResourceEditorInput | undefined; + + if (data) { + notebookUri = data.notebook; + cellOptions = { resource, options }; + } + + const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + + const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString()); + return { + editor: editorInput!.editor, + options: notebookOptions + }; } ); } @@ -344,6 +361,7 @@ registerAction2(class extends Action2 { const editorGroupService = accessor.get(IEditorGroupsService); const historyService = accessor.get(IInteractiveHistoryService); const kernelService = accessor.get(INotebookKernelService); + const logService = accessor.get(ILogService); const group = columnToEditorGroup(editorGroupService, typeof showOptions === 'number' ? showOptions : showOptions?.viewColumn); const editorOptions = { activation: EditorActivation.PRESERVE, @@ -351,9 +369,11 @@ registerAction2(class extends Action2 { }; if (resource && resource.scheme === Schemas.vscodeInteractive) { + logService.debug('Open interactive window from resource:', resource.toString()); const resourceUri = URI.revive(resource); const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); if (editors.length) { + logService.debug('Find existing interactive window:', resource.toString()); const editorInput = editors[0].editor as InteractiveEditorInput; const currentGroup = editors[0].groupId; const editor = await editorService.openEditor(editorInput, editorOptions, currentGroup); @@ -384,6 +404,8 @@ registerAction2(class extends Action2 { counter++; } while (existingNotebookDocument.has(notebookUri.toString())); + logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString()); + if (id) { const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, viewType: 'interactive' }).all; const preferredKernel = allKernels.find(kernel => kernel.id === id); @@ -397,6 +419,7 @@ registerAction2(class extends Action2 { const editorPane = await editorService.openEditor(editorInput, editorOptions, group); const editorControl = editorPane?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; // Extensions must retain references to these URIs to manipulate the interactive editor + logService.debug('New interactive window opened. Notebook editor id', editorControl?.notebookEditor?.getId()); return { notebookUri, inputUri, notebookEditorId: editorControl?.notebookEditor?.getId() }; } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index c6e561c2c4d..ca1f164befa 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -22,7 +22,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; -import { ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; @@ -501,6 +501,11 @@ export class InteractiveEditor extends EditorPane { this.#syncWithKernel(); } + override setOptions(options: INotebookEditorOptions | undefined): void { + this.#notebookWidget.value?.setOptions(options); + super.setOptions(options); + } + #toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason { switch (e.source) { case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC; @@ -620,7 +625,7 @@ export class InteractiveEditor extends EditorPane { }); } - this.#codeEditorWidget.setDecorations('interactive-decoration', DECORATION_KEY, decorations); + this.#codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); } override focus() { diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts index a931e2bb2f2..ef593cb3de1 100644 --- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts +++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts @@ -123,7 +123,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: detectLanguageCommandId, - title: localize('detectlang', 'Detect Language from Content'), + title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 661059e89b9..63b120d3b3c 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -398,8 +398,14 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'editor.inlayHints.Reset', - title: localize('reset', 'Reset Language Status Interaction Counter'), - category: localize('cat', 'View'), + title: { + value: localize('reset', 'Reset Language Status Interaction Counter'), + original: 'Reset Language Status Interaction Counter' + }, + category: { + value: localize('cat', 'View'), + original: 'View' + }, f1: true }); } diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts index c59d84821b2..4768a1752b4 100644 --- a/src/vs/workbench/contrib/localization/browser/localeService.ts +++ b/src/vs/workbench/contrib/localization/browser/localeService.ts @@ -3,31 +3,62 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { language } from 'vs/base/common/platform'; +import { localize } from 'vs/nls'; +import { Language } from 'vs/base/common/platform'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; export class WebLocaleService implements ILocaleService { declare readonly _serviceBrand: undefined; - async setLocale(languagePackItem: ILanguagePackItem): Promise { + constructor( + @IDialogService private readonly dialogService: IDialogService, + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService + ) { } + + async setLocale(languagePackItem: ILanguagePackItem): Promise { const locale = languagePackItem.id; - if (locale === language || (!locale && language === navigator.language)) { - return false; + if (locale === Language.value() || (!locale && Language.value() === navigator.language)) { + return; } if (locale) { window.localStorage.setItem('vscode.nls.locale', locale); } else { window.localStorage.removeItem('vscode.nls.locale'); } - return true; + + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('relaunchDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label), + primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); + } } - async clearLocalePreference(): Promise { - if (language === navigator.language) { - return false; - } + async clearLocalePreference(): Promise { window.localStorage.removeItem('vscode.nls.locale'); - return true; + + if (Language.value() === navigator.language) { + return; + } + + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('clearDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."), + primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); + } } } diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts index 5bdb7500724..2be6435942d 100644 --- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -5,9 +5,6 @@ import { localize } from 'vs/nls'; import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -15,8 +12,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; -const restart = localize('restart', "&&Restart"); - export class ConfigureDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.configureLocale'; public static readonly LABEL = localize('configureLocale', "Configure Display Language"); @@ -34,9 +29,6 @@ export class ConfigureDisplayLanguageAction extends Action2 { public async run(accessor: ServicesAccessor): Promise { const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService); const quickInputService: IQuickInputService = accessor.get(IQuickInputService); - const hostService: IHostService = accessor.get(IHostService); - const dialogService: IDialogService = accessor.get(IDialogService); - const productService: IProductService = accessor.get(IProductService); const localeService: ILocaleService = accessor.get(ILocaleService); const installedLanguages = await languagePackService.getInstalledLanguages(); @@ -72,19 +64,7 @@ export class ConfigureDisplayLanguageAction extends Action2 { disposables.add(qp.onDidAccept(async () => { const selectedLanguage = qp.activeItems[0]; qp.hide(); - - if (await localeService.setLocale(selectedLanguage)) { - const restartDialog = await dialogService.confirm({ - type: 'info', - message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), - primaryButton: restart - }); - - if (restartDialog.confirmed) { - hostService.restart(); - } - } + await localeService.setLocale(selectedLanguage); })); qp.show(); @@ -108,21 +88,6 @@ export class ClearDisplayLanguageAction extends Action2 { public async run(accessor: ServicesAccessor): Promise { const localeService: ILocaleService = accessor.get(ILocaleService); - const dialogService: IDialogService = accessor.get(IDialogService); - const productService: IProductService = accessor.get(IProductService); - const hostService: IHostService = accessor.get(IHostService); - - if (await localeService.clearLocalePreference()) { - const restartDialog = await dialogService.confirm({ - type: 'info', - message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), - primaryButton: restart - }); - - if (restartDialog.confirmed) { - hostService.restart(); - } - } + await localeService.clearLocalePreference(); } } diff --git a/src/vs/workbench/contrib/localization/common/locale.ts b/src/vs/workbench/contrib/localization/common/locale.ts index f447d40bc41..f2e8417c443 100644 --- a/src/vs/workbench/contrib/localization/common/locale.ts +++ b/src/vs/workbench/contrib/localization/common/locale.ts @@ -10,6 +10,6 @@ export const ILocaleService = createDecorator('localizationServi export interface ILocaleService { readonly _serviceBrand: undefined; - setLocale(languagePackItem: ILanguagePackItem): Promise; - clearLocalePreference(): Promise; + setLocale(languagePackItem: ILanguagePackItem): Promise; + clearLocalePreference(): Promise; } diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts index 00a6ec7036f..59b10dc5fcf 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { language } from 'vs/base/common/platform'; +import { Language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -19,6 +19,9 @@ import { toAction } from 'vs/base/common/actions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { stripComments } from 'vs/base/common/stripComments'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/productService'; export class NativeLocaleService implements ILocaleService { _serviceBrand: undefined; @@ -32,7 +35,10 @@ export class NativeLocaleService implements ILocaleService { @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IProgressService private readonly progressService: IProgressService, @ITextFileService private readonly textFileService: ITextFileService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IDialogService private readonly dialogService: IDialogService, + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService ) { } private async validateLocaleFile(): Promise { @@ -69,10 +75,10 @@ export class NativeLocaleService implements ILocaleService { return true; } - async setLocale(languagePackItem: ILanguagePackItem): Promise { + async setLocale(languagePackItem: ILanguagePackItem): Promise { const locale = languagePackItem.id; - if (locale === language || (!locale && language === 'en')) { - return false; + if (locale === Language.value() || (!locale && Language.isDefaultVariant())) { + return; } const installedLanguages = await this.languagePackService.getInstalledLanguages(); try { @@ -87,7 +93,7 @@ export class NativeLocaleService implements ILocaleService { // as of now, there are no 3rd party language packs available on the Marketplace. const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar); (viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`); - return false; + return; } await this.progressService.withProgress( @@ -102,22 +108,40 @@ export class NativeLocaleService implements ILocaleService { ); } - return await this.writeLocaleValue(locale); + if (await this.writeLocaleValue(locale)) { + await this.showRestartDialog(languagePackItem.label); + } } catch (err) { this.notificationService.error(err); - return false; } } - async clearLocalePreference(): Promise { - if (language === 'en') { - return false; - } + async clearLocalePreference(): Promise { try { - return await this.writeLocaleValue(undefined); + await this.writeLocaleValue(undefined); + if (!Language.isDefaultVariant()) { + await this.showRestartDialog('English'); + } } catch (err) { this.notificationService.error(err); - return false; + } + } + + private async showRestartDialog(languageName: string) { + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('restartDisplayLanguageMessage', "{0} needs to restart to change the display language", this.productService.nameLong), + detail: localize( + 'restartDisplayLanguageDetail', + "Press the restart button to restart {0} and set the display language to {1}.", + this.productService.nameLong, + languageName + ), + primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); } } } diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts index 9ba3e7aa0ee..4342dd4a740 100644 --- a/src/vs/workbench/contrib/logs/common/logConstants.ts +++ b/src/vs/workbench/contrib/logs/common/logConstants.ts @@ -9,5 +9,6 @@ export const rendererLogChannelId = 'rendererLog'; export const extHostLogChannelId = 'extHostLog'; export const telemetryLogChannelId = 'telemetryLog'; export const userDataSyncLogChannelId = 'userDataSyncLog'; +export const editSessionsLogChannelId = 'editSessionsSyncLog'; export const showWindowLogActionId = 'workbench.action.showWindowLog'; diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 5e533149305..f45fe793b09 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -38,6 +38,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { private registerCommonContributions(): void { this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Settings Sync"), this.environmentService.userDataSyncLogResource); + this.registerLogChannel(Constants.editSessionsLogChannelId, nls.localize('editSessionsLog', "Edit Sessions"), this.environmentService.editSessionsLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); const registerTelemetryChannel = () => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 8814e2435be..892febf378e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -6,20 +6,22 @@ import { Codicon } from 'vs/base/common/codicons'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { ctxIsMergeEditor, ctxMergeEditorLayout } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class OpenMergeEditor extends Action2 { constructor() { super({ id: '_open.mergeEditor', - title: localize('title', "Open Merge Editor"), + title: { value: localize('title', "Open Merge Editor"), original: 'Open Merge Editor' }, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -111,14 +113,20 @@ export class SetMixedLayout extends Action2 { constructor() { super({ id: 'merge.mixedLayout', - title: localize('layout.mixed', "Mixed Layout"), - toggled: ContextKeyExpr.equals(ctxMergeEditorLayout.key, 'mixed'), - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: '1_merge', - order: 9, - }] + title: { + value: localize('layout.mixed', 'Mixed Layout'), + original: 'Mixed Layout', + }, + toggled: ctxMergeEditorLayout.isEqualTo('mixed'), + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: '1_merge', + order: 9, + }, + ], + precondition: ctxIsMergeEditor, }); } @@ -134,14 +142,15 @@ export class SetColumnLayout extends Action2 { constructor() { super({ id: 'merge.columnLayout', - title: localize('layout.column', "Column Layout"), - toggled: ContextKeyExpr.equals(ctxMergeEditorLayout.key, 'columns'), + title: { value: localize('layout.column', "Column Layout"), original: 'Column Layout' }, + toggled: ctxMergeEditorLayout.isEqualTo('columns'), menu: [{ id: MenuId.EditorTitle, when: ctxIsMergeEditor, group: '1_merge', order: 10, - }] + }], + precondition: ctxIsMergeEditor, }); } @@ -153,26 +162,37 @@ export class SetColumnLayout extends Action2 { } } +const mergeEditorCategory: ILocalizedString = { + value: localize('mergeEditor', 'Merge Editor'), + original: 'Merge Editor', +}; + export class GoToNextConflict extends Action2 { constructor() { super({ id: 'merge.goToNextConflict', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.goToNextConflict', "Go to Next Conflict"), + category: mergeEditorCategory, + title: { + value: localize('merge.goToNextConflict', 'Go to Next Conflict'), + original: 'Go to Next Conflict', + }, icon: Codicon.arrowDown, - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: 'navigation', - }], + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation', + }, + ], f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToNextConflict(); + activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true); } } } @@ -181,22 +201,31 @@ export class GoToPreviousConflict extends Action2 { constructor() { super({ id: 'merge.goToPreviousConflict', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.goToPreviousConflict', "Go to Previous Conflict"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.goToPreviousConflict', + 'Go to Previous Conflict' + ), + original: 'Go to Previous Conflict', + }, icon: Codicon.arrowUp, - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: 'navigation', - }], + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation', + }, + ], f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToPreviousConflict(); + activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true); } } } @@ -205,9 +234,16 @@ export class ToggleActiveConflictInput1 extends Action2 { constructor() { super({ id: 'merge.toggleActiveConflictInput1', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleActiveConflictInput1', "Toggle Active Conflict In Input 1"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.toggleCurrentConflictFromLeft', + 'Toggle Current Conflict from Left' + ), + original: 'Toggle Current Conflict from Left', + }, f1: true, + precondition: ctxIsMergeEditor, }); } @@ -227,9 +263,16 @@ export class ToggleActiveConflictInput2 extends Action2 { constructor() { super({ id: 'merge.toggleActiveConflictInput2', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleActiveConflictInput2', "Toggle Active Conflict In Input 2"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.toggleCurrentConflictFromRight', + 'Toggle Current Conflict from Right' + ), + original: 'Toggle Current Conflict from Right', + }, f1: true, + precondition: ctxIsMergeEditor, }); } @@ -249,9 +292,16 @@ export class CompareInput1WithBaseCommand extends Action2 { constructor() { super({ id: 'mergeEditor.compareInput1WithBase', - category: localize('mergeEditor', "Merge Editor"), - title: localize('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"), + category: mergeEditorCategory, + title: { + value: localize( + 'mergeEditor.compareInput1WithBase', + 'Compare Input 1 With Base' + ), + original: 'Compare Input 1 With Base', + }, f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -265,9 +315,16 @@ export class CompareInput2WithBaseCommand extends Action2 { constructor() { super({ id: 'mergeEditor.compareInput2WithBase', - category: localize('mergeEditor', "Merge Editor"), - title: localize('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"), + category: mergeEditorCategory, + title: { + value: localize( + 'mergeEditor.compareInput2WithBase', + 'Compare Input 2 With Base' + ), + original: 'Compare Input 2 With Base', + }, f1: true, + precondition: ctxIsMergeEditor, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -294,3 +351,30 @@ function mergeEditorCompare(editorService: IEditorService, commandService: IComm function openDiffEditor(commandService: ICommandService, left: URI, right: URI, label?: string) { commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, left, right, label); } + +export class OpenBaseFile extends Action2 { + constructor() { + super({ + id: 'merge.openBaseEditor', + category: mergeEditorCategory, + title: { + value: localize('merge.openBaseEditor', 'Open Base File'), + original: 'Open Base File', + }, + f1: true, + precondition: ctxIsMergeEditor, + }); + } + + run(accessor: ServicesAccessor): void { + const openerService = accessor.get(IOpenerService); + const { activeEditorPane } = accessor.get(IEditorService); + if (activeEditorPane instanceof MergeEditor) { + const vm = activeEditorPane.viewModel.get(); + if (!vm) { + return; + } + openerService.open(vm.model.base.uri); + } + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 1ca1ed09565..6728dc93b1a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -18,6 +18,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; import { URI } from 'vs/base/common/uri'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; interface MergeEditorContents { languageId: string; @@ -28,13 +29,20 @@ interface MergeEditorContents { } export class MergeEditorCopyContentsToJSON extends Action2 { - constructor() { super({ id: 'merge.dev.copyContents', - title: localize('merge.dev.copyContents', "Developer Merge Editor: Copy Contents of Inputs, Base and Result as JSON"), + category: 'Merge Editor (Dev)', + title: { + value: localize( + 'merge.dev.copyContents', + 'Copy Contents of Inputs, Base and Result as JSON' + ), + original: 'Copy Contents of Inputs, Base and Result as JSON', + }, icon: Codicon.layoutCentered, f1: true, + precondition: ctxIsMergeEditor, }); } @@ -72,11 +80,17 @@ export class MergeEditorCopyContentsToJSON extends Action2 { } export class MergeEditorOpenContents extends Action2 { - constructor() { super({ id: 'merge.dev.openContents', - title: localize('merge.dev.openContents', "Developer Merge Editor: Open Contents of Inputs, Base and Result from JSON"), + category: 'Merge Editor (Dev)', + title: { + value: localize( + 'merge.dev.openContents', + 'Open Contents of Inputs, Base and Result from JSON' + ), + original: 'Open Contents of Inputs, Base and Result from JSON', + }, icon: Codicon.layoutCentered, f1: true, }); @@ -94,11 +108,14 @@ export class MergeEditorOpenContents extends Action2 { prompt: localize('mergeEditor.enterJSON', 'Enter JSON'), value: await clipboardService.readText(), }); - if (!result) { + if (result === undefined) { return; } - const content: MergeEditorContents = JSON.parse(result); + const content: MergeEditorContents = + result !== '' + ? JSON.parse(result) + : { base: '', input1: '', input2: '', result: '', languageId: 'plaintext' }; const scheme = 'merge-editor-dev'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 136249c41fa..8dc07e6463f 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -9,7 +9,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; @@ -34,6 +34,7 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit registerAction2(SetMixedLayout); registerAction2(SetColumnLayout); registerAction2(OpenMergeEditor); +registerAction2(OpenBaseFile); registerAction2(MergeEditorCopyContentsToJSON); registerAction2(MergeEditorOpenContents); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 7a248a0125b..053c7309754 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -5,18 +5,24 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; +import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; +import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { assertType } from 'vs/base/common/types'; +import { Event } from 'vs/base/common/event'; export class MergeEditorInputData { constructor( @@ -33,6 +39,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements private _model?: MergeEditorModel; private _outTextModel?: ITextFileEditorModel; + private _ignoreUnhandledConflictsForDirtyState?: true; constructor( public readonly base: URI, @@ -41,6 +48,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements public readonly result: URI, @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService private readonly _textModelService: ITextModelService, + @IDialogService private readonly _dialogService: IDialogService, + @IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService, @IEditorService editorService: IEditorService, @ITextFileService textFileService: ITextFileService, @ILabelService labelService: ILabelService, @@ -102,7 +111,11 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this.input2.title, this.input2.detail, this.input2.description, - result.object.textEditorModel + result.object.textEditorModel, + this._instaService.createInstance(EditorWorkerServiceDiffComputer), + { + resetUnknownOnInitialization: true + }, ); await this._model.onInitialized; @@ -112,7 +125,11 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this._store.add(input1); this._store.add(input2); this._store.add(result); + + this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined))); } + + this._ignoreUnhandledConflictsForDirtyState = undefined; return this._model; } @@ -129,7 +146,113 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements // ---- FileEditorInput override isDirty(): boolean { - return Boolean(this._outTextModel?.isDirty()); + const textModelDirty = Boolean(this._outTextModel?.isDirty()); + if (textModelDirty) { + // text model dirty -> 3wm is dirty + return true; + } + if (!this._ignoreUnhandledConflictsForDirtyState) { + // unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input + // to ignore unhandled conflicts for the dirty-state. This happens only + // after confirming to ignore unhandled changes + return Boolean(this._model && this._model.hasUnhandledConflicts.get()); + } + return false; + } + + override async confirm(editors?: ReadonlyArray): Promise { + + const inputs: MergeEditorInput[] = [this]; + if (editors) { + for (const { editor } of editors) { + if (editor instanceof MergeEditorInput) { + inputs.push(editor); + } + } + } + + const inputsWithUnhandledConflicts = inputs + .filter(input => input._model && input._model.hasUnhandledConflicts.get()); + + if (inputsWithUnhandledConflicts.length === 0) { + return ConfirmResult.SAVE; + } + + const actions: string[] = []; + const options = { + cancelId: 0, + detail: inputs.length > 1 + ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length) + : localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.') + }; + + const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF; + if (!isAnyAutoSave) { + // manual-save: FYI and discard + actions.push( + localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 + localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2 + ); + + } else { + // auto-save: only FYI + actions.push( + localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 + ); + } + + actions.push(localize('unhandledConflicts.cancel', "Cancel")); + options.cancelId = actions.length - 1; + + const { choice } = await this._dialogService.show( + Severity.Info, + localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1 + actions, + options + ); + + if (choice === options.cancelId) { + // cancel: stay in editor + return ConfirmResult.CANCEL; + } + + // save or revert: in both cases we tell the inputs to ignore unhandled conflicts + // for the dirty state computation. + for (const input of inputs) { + input._ignoreUnhandledConflictsForDirtyState = true; + } + + if (choice === 0) { + // conflicts: continue with remaining conflicts + return ConfirmResult.SAVE; + + } else if (choice === 1) { + // discard: undo all changes and save original (pre-merge) state + for (const input of inputs) { + input._discardMergeChanges(); + } + return ConfirmResult.SAVE; + + } else { + // don't save + return ConfirmResult.DONT_SAVE; + } + } + + private _discardMergeChanges(): void { + assertType(this._model !== undefined); + + const chunks: string[] = []; + while (true) { + const chunk = this._model.resultSnapshot.read(); + if (chunk === null) { + break; + } + chunks.push(chunk); + } + this._model.result.setValue(chunks.join()); } setLanguageId(languageId: string, _setExplicitly?: boolean): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts index 66116c85bb8..06401136b7a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts @@ -24,7 +24,7 @@ export class EditorWorkerServiceDiffComputer implements IDiffComputer { async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise { const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000); - if (!diffs || diffs.quitEarly) { + if (!diffs) { return { diffs: null }; } return { diffs: EditorWorkerServiceDiffComputer.fromDiffComputationResult(diffs, textModel1, textModel2) }; @@ -53,7 +53,7 @@ function fromLineChange(lineChange: ILineChange, originalTextModel: ITextModel, } let innerDiffs = lineChange.charChanges?.map(c => rangeMappingFromCharChange(c, originalTextModel, modifiedTextModel)).filter(isDefined); - if (!innerDiffs) { + if (!innerDiffs || innerDiffs.length === 0) { innerDiffs = [rangeMappingFromLineRanges(originalRange, modifiedRange)]; } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 3165fafc2f0..255615448d3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -3,25 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, CompareResult, tieBreakComparators, equals, numberComparator } from 'vs/base/common/arrays'; +import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { splitLines } from 'vs/base/common/strings'; -import { Constants } from 'vs/base/common/uint'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; -import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; -import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; -import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; -import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs'; -import { concatArrays, leftJoin, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; -import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange'; -import { IModelService } from 'vs/editor/common/services/model'; +import { ISettableObservable, derived, waitForState, observableValue, keepAlive, autorunHandleChanges, transaction, IReader, ITransaction, IObservable } from 'vs/base/common/observable'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/model'; +import { EditorModel } from 'vs/workbench/common/editor/editorModel'; +import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; +import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; +import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs'; +import { leftJoin } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange'; export const enum MergeEditorModelState { initializing = 1, @@ -30,12 +24,11 @@ export const enum MergeEditorModelState { } export class MergeEditorModel extends EditorModel { - private readonly diffComputer = new EditorWorkerServiceDiffComputer(this.editorWorkerService); private readonly input1TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input1, this.diffComputer)); private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer)); private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer)); - public readonly state = derivedObservable('state', reader => { + public readonly state = derived('state', reader => { const states = [ this.input1TextModelDiffs, this.input2TextModelDiffs, @@ -51,11 +44,11 @@ export class MergeEditorModel extends EditorModel { return MergeEditorModelState.upToDate; }); - public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate); + public readonly isUpToDate = derived('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate); - public readonly modifiedBaseRanges = derivedObservable('modifiedBaseRanges', (reader) => { + public readonly modifiedBaseRanges = derived('modifiedBaseRanges', (reader) => { const input1Diffs = this.input1TextModelDiffs.diffs.read(reader); const input2Diffs = this.input2TextModelDiffs.diffs.read(reader); @@ -67,22 +60,22 @@ export class MergeEditorModel extends EditorModel { public readonly resultDiffs = this.resultTextModelDiffs.diffs; private readonly modifiedBaseRangeStateStores = - derivedObservable('modifiedBaseRangeStateStores', reader => { + derived('modifiedBaseRangeStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeState${s.baseRange}`, ModifiedBaseRangeState.default)])) ); return map; }); private readonly modifiedBaseRangeHandlingStateStores = - derivedObservable('modifiedBaseRangeHandlingStateStores', reader => { + derived('modifiedBaseRangeHandlingStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeHandledState${s.baseRange}`, false)])) ); return map; }); - public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => { + public readonly unhandledConflictsCount = derived('unhandledConflictsCount', reader => { const map = this.modifiedBaseRangeHandlingStateStores.read(reader); let handledCount = 0; for (const [_key, value] of map) { @@ -91,7 +84,9 @@ export class MergeEditorModel extends EditorModel { return map.size - handledCount; }); - public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => { + public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0); + + public readonly input1ResultMapping = derived('input1ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount()); @@ -108,7 +103,7 @@ export class MergeEditorModel extends EditorModel { ); }); - public readonly input2ResultMapping = derivedObservable('input2ResultMapping', reader => { + public readonly input2ResultMapping = derived('input2ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input2LinesDiffs.read(reader), resultDiffs, this.input2.getLineCount()); @@ -125,6 +120,8 @@ export class MergeEditorModel extends EditorModel { ); }); + readonly resultSnapshot: ITextSnapshot; + constructor( readonly base: ITextModel, readonly input1: ITextModel, @@ -136,12 +133,14 @@ export class MergeEditorModel extends EditorModel { readonly input2Detail: string | undefined, readonly input2Description: string | undefined, readonly result: ITextModel, - @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, + private readonly diffComputer: IDiffComputer, + options: { resetUnknownOnInitialization: boolean }, @IModelService private readonly modelService: IModelService, @ILanguageService private readonly languageService: ILanguageService, ) { super(); + this.resultSnapshot = result.createSnapshot(); this._register(keepAlive(this.modifiedBaseRangeStateStores)); this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores)); this._register(keepAlive(this.input1ResultMapping)); @@ -150,7 +149,7 @@ export class MergeEditorModel extends EditorModel { let shouldResetHandlingState = true; this._register( autorunHandleChanges( - 'Recompute State', + 'Merge Editor Model: Recompute State', { handleChange: (ctx) => { if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) { @@ -170,6 +169,7 @@ export class MergeEditorModel extends EditorModel { const resultDiffs = this.resultTextModelDiffs.diffs.read(reader); const stores = this.modifiedBaseRangeStateStores.read(reader); transaction(tx => { + /** @description Merge Editor Model: Recompute State */ this.recomputeState(resultDiffs, stores, tx); if (shouldResetHandlingState) { shouldResetHandlingState = false; @@ -184,16 +184,18 @@ export class MergeEditorModel extends EditorModel { ) ); - this.onInitialized.then(() => { - this.resetUnknown(); - }); + if (options.resetUnknownOnInitialization) { + this.onInitialized.then(() => { + this.resetUnknown(); + }); + } } public getRangeInResult(baseRange: LineRange, reader?: IReader): LineRange { return this.resultTextModelDiffs.getResultRange(baseRange, reader); } - private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { + private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { const baseRangeWithStoreAndTouchingDiffs = leftJoin( stores, resultDiffs, @@ -207,12 +209,16 @@ export class MergeEditorModel extends EditorModel { ); for (const row of baseRangeWithStoreAndTouchingDiffs) { - row.left[1].set(this.computeState(row.left[0], row.rights), tx); + const newState = this.computeState(row.left[0], row.rights); + if (!row.left[1].get().equals(newState)) { + row.left[1].set(newState, tx); + } } } public resetUnknown(): void { transaction(tx => { + /** @description Reset Unknown Base Range States */ for (const range of this.modifiedBaseRanges.get()) { if (this.getState(range).get().conflicting) { this.setState(range, ModifiedBaseRangeState.default, false, tx); @@ -223,6 +229,7 @@ export class MergeEditorModel extends EditorModel { public mergeNonConflictingDiffs(): void { transaction((tx) => { + /** @description Merge None Conflicting Diffs */ for (const m of this.modifiedBaseRanges.get()) { if (m.isConflicting) { continue; @@ -269,7 +276,7 @@ export class MergeEditorModel extends EditorModel { this.resultTextModelDiffs.removeDiffs(conflictingDiffs, transaction); } - const { edit, effectiveState } = getEditForBase(baseRange, state); + const { edit, effectiveState } = baseRange.getEditForBase(state); existingState.set(effectiveState, transaction); @@ -312,7 +319,7 @@ export class MergeEditorModel extends EditorModel { ]; for (const s of states) { - const { edit } = getEditForBase(baseRange, s); + const { edit } = baseRange.getEditForBase(s); if (edit) { const resultRange = this.resultTextModelDiffs.getResultRange(baseRange.baseRange); const existingLines = resultRange.getLines(this.result); @@ -335,111 +342,10 @@ export class MergeEditorModel extends EditorModel { } public setLanguageId(languageId: string): void { - this.modelService.setMode(this.result, this.languageService.createById(languageId)); + const language = this.languageService.createById(languageId); + this.modelService.setMode(this.base, language); + this.modelService.setMode(this.input1, language); + this.modelService.setMode(this.input2, language); + this.modelService.setMode(this.result, language); } } - -function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } { - const diffs = concatArrays( - state.input1 && baseRange.input1CombinedDiff ? [{ diff: baseRange.input1CombinedDiff, inputNumber: 1 as const }] : [], - state.input2 && baseRange.input2CombinedDiff ? [{ diff: baseRange.input2CombinedDiff, inputNumber: 2 as const }] : [], - ); - - if (state.input2First) { - diffs.reverse(); - } - - const firstDiff = elementAtOrUndefined(diffs, 0); - const secondDiff = elementAtOrUndefined(diffs, 1); - - if (!firstDiff) { - return { edit: undefined, effectiveState: ModifiedBaseRangeState.default }; - } - if (!secondDiff) { - return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) }; - } - - const result = combineInputs(baseRange, state.input2First ? 2 : 1); - if (result) { - return { edit: result, effectiveState: state }; - } - - return { - edit: secondDiff.diff.getLineEdit(), - effectiveState: ModifiedBaseRangeState.default.withInputValue( - secondDiff.inputNumber, - true - ), - }; -} - -function combineInputs(baseRange: ModifiedBaseRange, firstInput: 1 | 2): LineRangeEdit | undefined { - const combinedDiffs = concatArrays( - baseRange.input1Diffs.flatMap((diffs) => - diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const })) - ), - baseRange.input2Diffs.flatMap((diffs) => - diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const })) - ) - ).sort( - tieBreakComparators( - compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts), - compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator) - ) - ); - - const sortedEdits = combinedDiffs.map(d => { - const sourceTextModel = d.input === 1 ? baseRange.input1TextModel : baseRange.input2TextModel; - return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange)); - }); - - return editsToLineRangeEdit(baseRange.baseRange, sortedEdits, baseRange.baseTextModel); -} - -function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined { - let text = ''; - const startsLineBefore = range.startLineNumber > 1; - let currentPosition = startsLineBefore - ? new Position( - range.startLineNumber - 1, - Constants.MAX_SAFE_SMALL_INTEGER - ) - : new Position(range.startLineNumber, 1); - - for (const edit of sortedEdits) { - const diffStart = edit.range.getStartPosition(); - if (!currentPosition.isBeforeOrEqual(diffStart)) { - return undefined; - } - let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart)); - if (diffStart.lineNumber > textModel.getLineCount()) { - // assert diffStart.lineNumber === textModel.getLineCount() + 1 - // getValueInRange doesn't include this virtual line break, as the document ends the line before. - // endsLineAfter will be false. - originalText += '\n'; - } - text += originalText; - text += edit.newText; - currentPosition = edit.range.getEndPosition(); - } - - const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount(); - const end = endsLineAfter ? new Position( - range.endLineNumberExclusive, - 1 - ) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER); - - const originalText = textModel.getValueInRange( - Range.fromPositions(currentPosition, end) - ); - text += originalText; - - const lines = splitLines(text); - if (startsLineBefore) { - lines.shift(); - } - if (endsLineAfter) { - lines.pop(); - } - return new LineRangeEdit(range, lines); -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts index 7befadb28bd..2e6b6031382 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts @@ -7,6 +7,13 @@ import { BugIndicatingError } from 'vs/base/common/errors'; import { ITextModel } from 'vs/editor/common/model'; import { DetailedLineRangeMapping, MappingAlignment } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { tieBreakComparators, compareBy, numberComparator } from 'vs/base/common/arrays'; +import { splitLines } from 'vs/base/common/strings'; +import { Constants } from 'vs/base/common/uint'; +import { LineRangeEdit, RangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; +import { concatArrays, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; /** * Describes modifications in input 1 and input 2 for a specific range in base. @@ -72,8 +79,142 @@ export class ModifiedBaseRange { public get isConflicting(): boolean { return this.input1Diffs.length > 0 && this.input2Diffs.length > 0; } + + public get canBeCombined(): boolean { + return this.combineInputs(1) !== undefined; + } + + public get isOrderRelevant(): boolean { + const input1 = this.combineInputs(1); + const input2 = this.combineInputs(2); + if (!input1 || !input2) { + return false; + } + return !input1.equals(input2); + } + + public getEditForBase(state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } { + const diffs = concatArrays( + state.input1 && this.input1CombinedDiff ? [{ diff: this.input1CombinedDiff, inputNumber: 1 as const }] : [], + state.input2 && this.input2CombinedDiff ? [{ diff: this.input2CombinedDiff, inputNumber: 2 as const }] : [], + ); + + if (state.input2First) { + diffs.reverse(); + } + + const firstDiff = elementAtOrUndefined(diffs, 0); + const secondDiff = elementAtOrUndefined(diffs, 1); + + if (!firstDiff) { + return { edit: undefined, effectiveState: ModifiedBaseRangeState.default }; + } + if (!secondDiff) { + return { edit: firstDiff.diff.getLineEdit(), effectiveState: ModifiedBaseRangeState.default.withInputValue(firstDiff.inputNumber, true) }; + } + + const result = this.combineInputs(state.input2First ? 2 : 1); + if (result) { + return { edit: result, effectiveState: state }; + } + + return { + edit: secondDiff.diff.getLineEdit(), + effectiveState: ModifiedBaseRangeState.default.withInputValue( + secondDiff.inputNumber, + true + ), + }; + } + + private input1LineRangeEdit: LineRangeEdit | undefined | null = null; + private input2LineRangeEdit: LineRangeEdit | undefined | null = null; + + private combineInputs(firstInput: 1 | 2): LineRangeEdit | undefined { + if (firstInput === 1 && this.input1LineRangeEdit !== null) { + return this.input1LineRangeEdit; + } else if (firstInput === 2 && this.input2LineRangeEdit !== null) { + return this.input2LineRangeEdit; + } + + const combinedDiffs = concatArrays( + this.input1Diffs.flatMap((diffs) => + diffs.rangeMappings.map((diff) => ({ diff, input: 1 as const })) + ), + this.input2Diffs.flatMap((diffs) => + diffs.rangeMappings.map((diff) => ({ diff, input: 2 as const })) + ) + ).sort( + tieBreakComparators( + compareBy((d) => d.diff.inputRange, Range.compareRangesUsingStarts), + compareBy((d) => (d.input === firstInput ? 1 : 2), numberComparator) + ) + ); + + const sortedEdits = combinedDiffs.map(d => { + const sourceTextModel = d.input === 1 ? this.input1TextModel : this.input2TextModel; + return new RangeEdit(d.diff.inputRange, sourceTextModel.getValueInRange(d.diff.outputRange)); + }); + + const result = editsToLineRangeEdit(this.baseRange, sortedEdits, this.baseTextModel); + if (firstInput === 1) { + this.input1LineRangeEdit = result; + } else { + this.input2LineRangeEdit = result; + } + return result; + } } +function editsToLineRangeEdit(range: LineRange, sortedEdits: RangeEdit[], textModel: ITextModel): LineRangeEdit | undefined { + let text = ''; + const startsLineBefore = range.startLineNumber > 1; + let currentPosition = startsLineBefore + ? new Position( + range.startLineNumber - 1, + Constants.MAX_SAFE_SMALL_INTEGER + ) + : new Position(range.startLineNumber, 1); + + for (const edit of sortedEdits) { + const diffStart = edit.range.getStartPosition(); + if (!currentPosition.isBeforeOrEqual(diffStart)) { + return undefined; + } + let originalText = textModel.getValueInRange(Range.fromPositions(currentPosition, diffStart)); + if (diffStart.lineNumber > textModel.getLineCount()) { + // assert diffStart.lineNumber === textModel.getLineCount() + 1 + // getValueInRange doesn't include this virtual line break, as the document ends the line before. + // endsLineAfter will be false. + originalText += '\n'; + } + text += originalText; + text += edit.newText; + currentPosition = edit.range.getEndPosition(); + } + + const endsLineAfter = range.endLineNumberExclusive <= textModel.getLineCount(); + const end = endsLineAfter ? new Position( + range.endLineNumberExclusive, + 1 + ) : new Position(range.endLineNumberExclusive - 1, Constants.MAX_SAFE_SMALL_INTEGER); + + const originalText = textModel.getValueInRange( + Range.fromPositions(currentPosition, end) + ); + text += originalText; + + const lines = splitLines(text); + if (startsLineBefore) { + lines.shift(); + } + if (endsLineAfter) { + lines.pop(); + } + return new LineRangeEdit(range, lines); +} + + export class ModifiedBaseRangeState { public static readonly default = new ModifiedBaseRangeState(false, false, false, false); public static readonly conflicting = new ModifiedBaseRangeState(false, false, false, true); @@ -140,18 +281,30 @@ export class ModifiedBaseRangeState { } public toString(): string { - const arr: ('1' | '2')[] = []; + const arr: string[] = []; if (this.input1) { - arr.push('1'); + arr.push('1✓'); } if (this.input2) { - arr.push('2'); + arr.push('2✓'); } if (this.input2First) { arr.reverse(); } + if (this.conflicting) { + arr.push('conflicting'); + } return arr.join(','); } + + equals(other: ModifiedBaseRangeState): boolean { + return ( + this.input1 === other.input1 && + this.input2 === other.input2 && + this.input2First === other.input2First && + this.conflicting === other.conflicting + ); + } } export const enum InputState { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index c57c0ff51ea..af21adbebb5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -7,17 +7,17 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; -import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { IDiffComputer } from './diffComputer'; +import { IObservable, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; export class TextModelDiffs extends Disposable { private updateCount = 0; - private readonly _state = new ObservableValue(TextModelDiffState.initializing, 'LiveDiffState'); - private readonly _diffs = new ObservableValue([], 'LiveDiffs'); + private readonly _state = observableValue('LiveDiffState', TextModelDiffState.initializing); + private readonly _diffs = observableValue('LiveDiffs', []); private readonly barrier = new ReentrancyBarrier(); private isDisposed = false; @@ -54,6 +54,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Starting Diff Computation. */ this._state.set( initializing ? TextModelDiffState.initializing : TextModelDiffState.updating, tx, @@ -72,6 +73,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Completed Diff Computation */ if (result.diffs) { this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange); this._diffs.set(result.diffs, tx, TextModelDiffChangeReason.textChange); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 308b6ab01ff..7e92f970387 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -5,11 +5,10 @@ import { CompareResult, ArrayQueue } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, autorun } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { IDisposable } from 'xterm'; export class ReentrancyBarrier { private isActive = false; @@ -74,12 +73,12 @@ function toSize(value: number | string): string { export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable): IDisposable { const d = new DisposableStore(); let decorationIds: string[] = []; - d.add(autorun(reader => { + d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => { const d = decorations.read(reader); editor.changeDecorations(a => { decorationIds = a.deltaDecorations(decorationIds, d); }); - }, 'Update Decorations')); + })); d.add({ dispose: () => { editor.changeDecorations(a => { @@ -143,3 +142,19 @@ export function thenIfNotDisposed(promise: Promise, then: () => void): IDi export function setFields(obj: T, fields: Partial): T { return Object.assign(obj, fields); } + +export function deepMerge(source1: T, source2: Partial): T { + const result = {} as T; + for (const key in source1) { + result[key] = source1[key]; + } + for (const key in source2) { + const source2Value = source2[key]; + if (typeof result[key] === 'object' && source2Value && typeof source2Value === 'object') { + result[key] = deepMerge(result[key], source2Value); + } else { + result[key] = source2Value as any; + } + } + return result; +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts index 99a4125d8e4..ac93d328631 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/colors.ts @@ -7,114 +7,50 @@ import { localize } from 'vs/nls'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; export const diff = registerColor( - 'mergeEditor.diff', - { - dark: '#9bb95533', - light: '#9bb95533', - hcDark: '#9bb95533', - hcLight: '#9bb95533', - }, - localize( - 'mergeEditor.diff', - 'The foreground color for changes.' - ) + 'mergeEditor.change.background', + { dark: '#9bb95533', light: '#9bb95533', hcDark: '#9bb95533', hcLight: '#9bb95533', }, + localize('mergeEditor.change.background', 'The background color for changes.') ); export const diffWord = registerColor( - 'mergeEditor.diff.word', - { - dark: '#9bb9551e', - light: '#9bb9551e', - hcDark: '#9bb9551e', - hcLight: '#9bb9551e', - }, - localize( - 'mergeEditor.diff.word', - 'The foreground color for word changes.' - ) + 'mergeEditor.change.word.background', + { dark: '#9bb9551e', light: '#9bb9551e', hcDark: '#9bb9551e', hcLight: '#9bb9551e', }, + localize('mergeEditor.change.word.background', 'The background color for word changes.') ); export const conflictBorderUnhandledUnfocused = registerColor( - 'mergeEditor.conflictBorder.unhandledUnfocused', - { - dark: '#ffa6007a', - light: '#ffa6007a', - hcDark: '#ffa6007a', - hcLight: '#ffa6007a', - }, - localize( - 'mergeEditor.conflictBorder.unhandledUnfocused', - 'The border color of unhandled unfocused conflicts.' - ) + 'mergeEditor.conflict.unhandledUnfocused.border', + { dark: '#ffa6007a', light: '#ffa6007a', hcDark: '#ffa6007a', hcLight: '#ffa6007a', }, + localize('mergeEditor.conflict.unhandledUnfocused.border', 'The border color of unhandled unfocused conflicts.') ); export const conflictBorderUnhandledFocused = registerColor( - 'mergeEditor.conflictBorder.unhandledFocused', - { - dark: '#ffa600', - light: '#ffa600', - hcDark: '#ffa600', - hcLight: '#ffa600', - }, - localize( - 'mergeEditor.conflictBorder.unhandledFocused', - 'The border color of unhandled focused conflicts.' - ) + 'mergeEditor.conflict.unhandledFocused.border', + { dark: '#ffa600', light: '#ffa600', hcDark: '#ffa600', hcLight: '#ffa600', }, + localize('mergeEditor.conflict.unhandledFocused.border', 'The border color of unhandled focused conflicts.') ); export const conflictBorderHandledUnfocused = registerColor( - 'mergeEditor.conflictBorder.handledUnfocused', - { - dark: '#86868649', - light: '#86868649', - hcDark: '#86868649', - hcLight: '#86868649', - }, - localize( - 'mergeEditor.conflictBorder.handledUnfocused', - 'The border color of handled unfocused conflicts.' - ) + 'mergeEditor.conflict.handledUnfocused.border', + { dark: '#86868649', light: '#86868649', hcDark: '#86868649', hcLight: '#86868649', }, + localize('mergeEditor.conflict.handledUnfocused.border', 'The border color of handled unfocused conflicts.') ); export const conflictBorderHandledFocused = registerColor( - 'mergeEditor.conflictBorder.handledFocused', - { - dark: '#c1c1c1cc', - light: '#c1c1c1cc', - hcDark: '#c1c1c1cc', - hcLight: '#c1c1c1cc', - }, - localize( - 'mergeEditor.conflictBorder.handledFocused', - 'The border color of handled focused conflicts.' - ) + 'mergeEditor.conflict.handledFocused.border', + { dark: '#c1c1c1cc', light: '#c1c1c1cc', hcDark: '#c1c1c1cc', hcLight: '#c1c1c1cc', }, + localize('mergeEditor.conflict.handledFocused.border', 'The border color of handled focused conflicts.') ); export const handledConflictMinimapOverViewRulerColor = registerColor( - 'mergeEditor.conflict-handled.minimapOverViewRuler', - { - dark: '#adaca8ee', - light: '#adaca8ee', - hcDark: '#adaca8ee', - hcLight: '#adaca8ee', - }, - localize( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - 'The foreground color for changes in input 1.' - ) + 'mergeEditor.conflict.handled.minimapOverViewRuler', + { dark: '#adaca8ee', light: '#adaca8ee', hcDark: '#adaca8ee', hcLight: '#adaca8ee', }, + localize('mergeEditor.conflict.handled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); export const unhandledConflictMinimapOverViewRulerColor = registerColor( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - { - dark: '#fcba03FF', - light: '#fcba03FF', - hcDark: '#fcba03FF', - hcLight: '#fcba03FF', - }, - localize( - 'mergeEditor.conflict-unhandled.minimapOverViewRuler', - 'The foreground color for changes in input 1.' - ) + 'mergeEditor.conflict.unhandled.minimapOverViewRuler', + { dark: '#fcba03FF', light: '#fcba03FF', hcDark: '#fcba03FF', hcLight: '#fcba03FF', }, + localize('mergeEditor.conflict.unhandled.minimapOverViewRuler', 'The foreground color for changes in input 1.') ); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index fee958633e5..7289cc61637 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -3,23 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { autorun, IReader, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { private readonly scrollTop = observableFromEvent( this._editor.onDidScrollChange, - (e) => this._editor.getScrollTop() + (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop() ); + private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0); private readonly modelAttached = observableFromEvent( this._editor.onDidChangeModel, - (e) => this._editor.hasModel() + (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel() ); - private readonly viewZoneChanges = new ObservableValue(0, 'counter'); + private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones); + private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange); constructor( private readonly _editor: CodeEditorWidget, @@ -27,12 +29,17 @@ export class EditorGutter extends D private readonly itemProvider: IGutterItemProvider ) { super(); - this._domNode.className = 'gutter'; - this._register(autorun((reader) => this.render(reader), 'Render')); + this._domNode.className = 'gutter monaco-editor'; + const scrollDecoration = this._domNode.appendChild( + h('div.scroll-decoration', { role: 'presentation', ariaHidden: true, style: { width: '100%' } }) + .root + ); - this._editor.onDidChangeViewZones(e => { - this.viewZoneChanges.set(this.viewZoneChanges.get() + 1, undefined); - }); + this._register(autorun('update scroll decoration', (reader) => { + scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration'; + })); + + this._register(autorun('EditorGutter.Render', (reader) => this.render(reader))); } private readonly views = new Map(); @@ -41,7 +48,10 @@ export class EditorGutter extends D if (!this.modelAttached.read(reader)) { return; } - this.viewZoneChanges.read(reader); + + this.editorOnDidChangeViewZones.read(reader); + this.editorOnDidContentSizeChange.read(reader); + const scrollTop = this.scrollTop.read(reader); const visibleRanges = this._editor.getVisibleRanges(); @@ -53,15 +63,13 @@ export class EditorGutter extends D const visibleRange2 = new LineRange( visibleRange.startLineNumber, visibleRange.endLineNumber - visibleRange.startLineNumber - ); + ).deltaEnd(1); const gutterItems = this.itemProvider.getIntersectingGutterItems( visibleRange2, reader ); - const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); - for (const gutterItem of gutterItems) { if (!gutterItem.range.touches(visibleRange2)) { continue; @@ -84,19 +92,10 @@ export class EditorGutter extends D } const top = - (gutterItem.range.startLineNumber === 1 - ? -lineHeight - : this._editor.getTopForLineNumber( - gutterItem.range.startLineNumber - 1 - )) - - scrollTop + - lineHeight; - - const bottom = ( - gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount() - ? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive) - : this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight - ) - scrollTop; + gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount() + ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop + : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop; + const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop; const height = bottom - top; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index a5c8bf676d5..f6a409656f8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -3,30 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { h } from 'vs/base/browser/dom'; +import { h, reset } from 'vs/base/browser/dom'; import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IObservable, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; -import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; -export interface ICodeEditorViewOptions { - readonly: boolean; -} - export abstract class CodeEditorView extends Disposable { - private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); + private readonly _viewModel = observableValue('viewModel', undefined); readonly viewModel: IObservable = this._viewModel; - readonly model = this._viewModel.map(m => m?.model); + readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', { $: 'title' }), + h('div.title', [ + h('span.title', { $: 'title' }), + h('span.description', { $: 'description' }), + h('span.detail', { $: 'detail' }), + ]), h('div.container', [ h('div.gutter', { $: 'gutterDiv' }), h('div', { $: 'editor' }), @@ -55,45 +57,42 @@ export abstract class CodeEditorView extends Disposable { // snap?: boolean | undefined; }; - private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true }); - protected readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true }); - public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, this.htmlElements.editor, + {}, { - minimap: { enabled: false }, - readOnly: this._options.readonly, - glyphMargin: false, - lineNumbersMinChars: 2, - }, - {} + contributions: this.getEditorContributions(), + } ); + public updateOptions(newOptions: Readonly): void { + this.editor.updateOptions(newOptions); + } + public readonly isFocused = observableFromEvent( Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget), - () => this.editor.hasWidgetFocus() + () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus() ); public readonly cursorPosition = observableFromEvent( this.editor.onDidChangeCursorPosition, - () => this.editor.getPosition() + () => /** @description editor.getPosition */ this.editor.getPosition() ); - public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber); - - /*derivedObservable('cursorLineNumber', reader => { - return this.cursorPosition.read(reader)?.lineNumber; - });*/ + public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber); constructor( - private readonly _options: ICodeEditorViewOptions, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } + protected getEditorContributions(): IEditorContributionDescription[] | undefined { + return undefined; + } + public setModel( viewModel: MergeEditorViewModel, textModel: ITextModel, @@ -102,9 +101,14 @@ export abstract class CodeEditorView extends Disposable { detail: string | undefined ): void { this.editor.setModel(textModel); - this._title.setLabel(title, description); - this._detail.setLabel('', detail); - this._viewModel.set(viewModel, undefined); + reset(this.htmlElements.title, ...renderLabelWithIcons(title)); + reset(this.htmlElements.description, ...(description ? renderLabelWithIcons(description) : [])); + reset(this.htmlElements.detail, ...(detail ? renderLabelWithIcons(detail) : [])); + + transaction(tx => { + /** @description CodeEditorView: Set Model */ + this._viewModel.set(viewModel, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index d7d2bb31291..abadef681ba 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -3,26 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; -import { Action, IAction } from 'vs/base/common/actions'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; +import { autorun, derived, IObservable, ISettableObservable, ITransaction, transaction, observableValue } from 'vs/base/common/observable'; import { noBreakWhitespace } from 'vs/base/common/strings'; +import { isDefined } from 'vs/base/common/types'; +import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { CodeLensContribution } from 'vs/editor/contrib/codelens/browser/codelensController'; import { localize } from 'vs/nls'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { attachToggleStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'; -import { CodeEditorView, ICodeEditorViewOptions } from './codeEditorView'; -import * as dom from 'vs/base/browser/dom'; -import { isDefined } from 'vs/base/common/types'; +import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derived(`input${this.inputNumber}.decorations`, reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -95,167 +99,216 @@ export class InputCodeEditorView extends CodeEditorView { return result; }); + private readonly modifiedBaseRangeGutterItemInfos = derived(`input${this.inputNumber}.modifiedBaseRangeGutterItemInfos`, reader => { + const viewModel = this.viewModel.read(reader); + if (!viewModel) { return []; } + const model = viewModel.model; + const inputNumber = this.inputNumber; + + return model.modifiedBaseRanges.read(reader) + .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + range: baseRange.getInputRange(this.inputNumber), + enabled: model.isUpToDate, + toggleState: derived('checkbox is checked', (reader) => { + const input = model + .getState(baseRange) + .read(reader) + .getInput(this.inputNumber); + return input === InputState.second && !baseRange.isOrderRelevant + ? InputState.first + : input; + } + ), + setState: (value, tx) => viewModel.setState( + baseRange, + model + .getState(baseRange) + .get() + .withInputValue(this.inputNumber, value), + tx + ), + toggleBothSides() { + transaction(tx => { + /** @description Context Menu: toggle both sides */ + const state = model + .getState(baseRange) + .get(); + model.setState( + baseRange, + state + .toggle(inputNumber) + .toggle(inputNumber === 1 ? 2 : 1), + true, + tx + ); + }); + }, + getContextMenuActions: () => { + const state = model.getState(baseRange).get(); + const handled = model.isHandled(baseRange).get(); + + const update = (newState: ModifiedBaseRangeState) => { + transaction(tx => { + /** @description Context Menu: Update Base Range State */ + return viewModel.setState(baseRange, newState, tx); + }); + }; + + function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { + const action = new Action(id, label, undefined, true, () => { + update(targetState); + }); + action.checked = checked; + return action; + } + const both = state.input1 && state.input2; + + return [ + baseRange.input1Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput1', + localize('mergeEditor.accept', 'Accept {0}', model.input1Title), + state.toggle(1), + state.input1 + ) + : undefined, + baseRange.input2Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput2', + localize('mergeEditor.accept', 'Accept {0}', model.input2Title), + state.toggle(2), + state.input2 + ) + : undefined, + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.acceptBoth', + localize( + 'mergeEditor.acceptBoth', + 'Accept Both' + ), + state.withInput1(!both).withInput2(!both), + both + ), + { enabled: baseRange.canBeCombined } + ) + : undefined, + new Separator(), + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.swap', + localize('mergeEditor.swap', 'Swap'), + state.swap(), + false + ), + { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } + ) + : undefined, + + setFields( + new Action( + 'mergeEditor.markAsHandled', + localize('mergeEditor.markAsHandled', 'Mark as Handled'), + undefined, + true, + () => { + transaction((tx) => { + /** @description Context Menu: Mark as handled */ + model.setHandled(baseRange, !handled, tx); + }); + } + ), + { checked: handled } + ), + ].filter(isDefined); + } + })); + }); + constructor( public readonly inputNumber: 1 | 2, - options: ICodeEditorViewOptions, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, + @IThemeService themeService: IThemeService, ) { - super(options, instantiationService); + super(instantiationService); this._register(applyObservableDecorations(this.editor, this.decorations)); this._register( new EditorGutter(this.editor, this.htmlElements.gutterDiv, { getIntersectingGutterItems: (range, reader) => { - const viewModel = this.viewModel.read(reader); - if (!viewModel) { return []; } - const model = viewModel.model; - - return model.modifiedBaseRanges.read(reader) - .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) - .map((baseRange, idx) => ({ - id: idx.toString(), - range: baseRange.getInputRange(this.inputNumber), - enabled: model.isUpToDate, - toggleState: derivedObservable('toggle', (reader) => model - .getState(baseRange) - .read(reader) - .getInput(this.inputNumber) - ), - setState: (value, tx) => viewModel.setState( - baseRange, - model - .getState(baseRange) - .get() - .withInputValue(this.inputNumber, value), - tx - ), - getContextMenuActions: () => { - const state = model.getState(baseRange).get(); - const handled = model.isHandled(baseRange).get(); - - const update = (newState: ModifiedBaseRangeState) => { - transaction(tx => viewModel.setState(baseRange, newState, tx)); - }; - - function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { - const action = new Action(id, label, undefined, true, () => { - update(targetState); - }); - action.checked = checked; - return action; - } - const both = state.input1 && state.input2; - - return [ - baseRange.input1Diffs.length > 0 - ? action( - 'mergeEditor.takeInput1', - localize('mergeEditor.takeInput1', 'Take Input 1'), - state.toggle(1), - state.input1 - ) - : undefined, - baseRange.input2Diffs.length > 0 - ? action( - 'mergeEditor.takeInput2', - localize('mergeEditor.takeInput2', 'Take Input 2'), - state.toggle(2), - state.input2 - ) - : undefined, - baseRange.isConflicting - ? action( - 'mergeEditor.takeBothSides', - localize( - 'mergeEditor.takeBothSides', - 'Take Both Sides' - ), - state.withInput1(!both).withInput2(!both), - both - ) - : undefined, - baseRange.isConflicting - ? setFields( - action( - 'mergeEditor.swap', - localize('mergeEditor.swap', 'Swap'), - state.swap(), - false - ), - { enabled: !state.isEmpty } - ) - : undefined, - - setFields( - new Action( - 'mergeEditor.markAsHandled', - localize('mergeEditor.markAsHandled', 'Mark as Handled'), - undefined, - true, - () => { - transaction((tx) => { - model.setHandled(baseRange, !handled, tx); - }); - } - ), - { checked: handled } - ), - ].filter(isDefined); - } - })); + return this.modifiedBaseRangeGutterItemInfos.read(reader); }, - createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService), + createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), }) ); } + + protected override getEditorContributions(): IEditorContributionDescription[] | undefined { + return EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeLensContribution.ID); + } } export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { enabled: IObservable; toggleState: IObservable; setState(value: boolean, tx: ITransaction): void; + toggleBothSides(): void; getContextMenuActions(): readonly IAction[]; } export class MergeConflictGutterItemView extends Disposable implements IGutterItemView { - private readonly item = new ObservableValue(undefined, 'item'); + private readonly item: ISettableObservable; constructor( item: ModifiedBaseRangeGutterItemInfo, private readonly target: HTMLElement, contextMenuService: IContextMenuService, + themeService: IThemeService ) { super(); - this.item.set(item, undefined); + this.item = observableValue('item', item); target.classList.add('merge-accept-gutter-marker'); - const checkBox = new Toggle({ isChecked: false, title: localize('acceptMerge', "Accept Merge"), icon: Codicon.check }); + const checkBox = new Toggle({ isChecked: false, title: localize('accept', "Accept"), icon: Codicon.check }); + + this._register(attachToggleStyler(checkBox, themeService)); + this._register( dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => { - if (e.button === 2) { - const item = this.item.get(); - if (!item) { - return; - } + const item = this.item.get(); + if (!item) { + return; + } + if (e.button === /* Right */ 2) { contextMenuService.showContextMenu({ getAnchor: () => checkBox.domNode, getActions: item.getContextMenuActions, }); + e.stopPropagation(); + e.preventDefault(); + } else if (e.button === /* Middle */ 1) { + item.toggleBothSides(); + e.stopPropagation(); e.preventDefault(); } }) ); + checkBox.domNode.classList.add('accept-conflict-group'); this._register( - autorun((reader) => { + autorun('Update Checkbox', (reader) => { const item = this.item.read(reader)!; const value = item.toggleState.read(reader); const iconMap: Record = { @@ -272,11 +325,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } else { checkBox.enable(); } - }, 'Update Toggle State') + }) ); this._register(checkBox.onChange(() => { transaction(tx => { + /** @description Handle Checkbox Change */ this.item.get()!.setState(checkBox.checked, tx); }); })); @@ -294,6 +348,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } update(baseRange: ModifiedBaseRangeGutterItemInfo): void { - this.item.set(baseRange, undefined); + transaction(tx => { + /** @description MergeConflictGutterItemView: Updating new base range */ + this.item.set(baseRange, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index 2037af7fa49..e1557eb0c9a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { CompareResult } from 'vs/base/common/arrays'; +import { autorun, derived } from 'vs/base/common/observable'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; -import { CodeEditorView, ICodeEditorViewOptions } from './codeEditorView'; +import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derived('result.decorations', reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -99,23 +100,32 @@ export class ResultCodeEditorView extends CodeEditorView { }); constructor( - options: ICodeEditorViewOptions, @IInstantiationService instantiationService: IInstantiationService ) { - super(options, instantiationService); + super(instantiationService); this._register(applyObservableDecorations(this.editor, this.decorations)); - this._register(autorun(reader => { + this._register(autorun('update remainingConflicts label', reader => { const model = this.model.read(reader); if (!model) { return; } const count = model.unhandledConflictsCount.read(reader); - // TODO @joh - this._detail.setLabel(`${count} Remaining Conflicts`); - }, 'update label')); + this.htmlElements.detail.innerText = count === 1 + ? localize( + 'mergeEditor.remainingConflicts', + '{0} Conflict Remaining', + count + ) + : localize( + 'mergeEditor.remainingConflict', + '{0} Conflicts Remaining ', + count + ); + + })); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css index 895a56245a6..e9c0ada7df3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css @@ -8,11 +8,27 @@ height: 30px; display: flex; align-content: center; - justify-content: space-between; } -.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label { - margin: auto 0; +.monaco-workbench .merge-editor .code-view > .title>SPAN { + align-self: center; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 6px; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.title { + flex-shrink: 0; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.description { + opacity: 0.7; + font-size: 90%; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.detail { + margin-left: auto; + flex-shrink: 0; } .monaco-workbench .merge-editor .code-view > .container { @@ -30,28 +46,28 @@ } .merge-editor-diff { - background-color: var(--vscode-mergeEditor-diff); + background-color: var(--vscode-mergeEditor-change-background); } .merge-editor-diff-word { - background-color: var(--vscode-mergeEditor-diff-word); + background-color: var(--vscode-mergeEditor-change-word-background); } .merge-editor-block { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-unhandledUnfocused); + border: 2px solid var(--vscode-mergeEditor-conflict-unhandledUnfocused-border); } .merge-editor-block.focused { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-unhandledFocused); + border: 2px solid var(--vscode-mergeEditor-conflict-unhandledFocused-border); } .merge-editor-block.handled { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-handledUnfocused); + border: 2px solid var(--vscode-mergeEditor-conflict-handledUnfocused-border); } .merge-editor-block.handled.focused { - border: 2px solid var(--vscode-mergeEditor-conflictBorder-handledFocused); + border: 2px solid var(--vscode-mergeEditor-conflict-handledFocused-border); } .gutter-item { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 6c618b81d56..544e07332cb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -10,20 +10,22 @@ import { IAction } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { localize } from 'vs/nls'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -36,12 +38,12 @@ import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; -import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; -import { ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { deepMerge, ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; +import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -50,12 +52,6 @@ import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; import { ResultCodeEditorView } from './editors/resultCodeEditorView'; -export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); -export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed'); -export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); - -export type MergeEditorLayoutTypes = 'mixed' | 'columns'; - class MergeEditorLayout { private static readonly _key = 'mergeEditor/layout'; @@ -83,18 +79,16 @@ class MergeEditorLayout { } } -export class MergeEditor extends AbstractTextEditor { +export class MergeEditor extends AbstractTextEditor { static readonly ID = 'mergeEditor'; private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - - - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1, { readonly: !this.inputsWritable })); - private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2, { readonly: !this.inputsWritable })); - private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView, { readonly: false })); + private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); + private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); + private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); private readonly _layoutMode: MergeEditorLayout; private readonly _ctxIsMergeEditor: IContextKey; @@ -142,6 +136,10 @@ export class MergeEditor extends AbstractTextEditor { synchronizeScrolling(this.input1View.editor, this.inputResultView.editor, mapping, MappingDirection.input); this.input2View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } + if (c.scrollLeftChanged) { + this.input2View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.inputResultView.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); @@ -153,6 +151,10 @@ export class MergeEditor extends AbstractTextEditor { synchronizeScrolling(this.input2View.editor, this.inputResultView.editor, mapping, MappingDirection.input); this.input1View.editor.setScrollTop(c.scrollTop, ScrollType.Immediate); } + if (c.scrollLeftChanged) { + this.input1View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.inputResultView.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); @@ -165,6 +167,10 @@ export class MergeEditor extends AbstractTextEditor { const mapping2 = this.model?.input2ResultMapping.get(); synchronizeScrolling(this.inputResultView.editor, this.input2View.editor, mapping2, MappingDirection.output); } + if (c.scrollLeftChanged) { + this.input1View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + this.input2View.editor.setScrollLeft(c.scrollLeft, ScrollType.Immediate); + } }) ) ); @@ -202,6 +208,19 @@ export class MergeEditor extends AbstractTextEditor { super.dispose(); } + // --- layout constraints + + private readonly _onDidChangeSizeConstraints = new Emitter(); + override readonly onDidChangeSizeConstraints: Event = this._onDidChangeSizeConstraints.event; + + override get minimumWidth() { + return this._layoutMode.value === 'mixed' + ? this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + : this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + this.inputResultView.view.minimumWidth; + } + + // --- + override getTitle(): string { if (this.input) { return this.input.getName(); @@ -250,9 +269,16 @@ export class MergeEditor extends AbstractTextEditor { } private applyOptions(options: ICodeEditorOptions): void { - this.input1View.editor.updateOptions({ ...options, readOnly: !this.inputsWritable }); - this.input2View.editor.updateOptions({ ...options, readOnly: !this.inputsWritable }); - this.inputResultView.editor.updateOptions(options); + const inputOptions: ICodeEditorOptions = deepMerge(options, { + minimap: { enabled: false }, + glyphMargin: false, + lineNumbersMinChars: 2, + readOnly: !this.inputsWritable + }); + + this.input1View.updateOptions(inputOptions); + this.input2View.updateOptions(inputOptions); + this.inputResultView.updateOptions(options); } protected getMainControl(): ICodeEditor | undefined { @@ -282,6 +308,9 @@ export class MergeEditor extends AbstractTextEditor { this.inputResultView.setModel(viewModel, model.result, localize('result', 'Result',), this._labelService.getUriLabel(model.result.uri, { relative: true }), undefined); this._ctxBaseResourceScheme.set(model.base.uri.scheme); + const viewState = this.loadEditorViewState(input, context); + this._applyViewState(viewState); + this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => { const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting); if (!firstConflict) { @@ -437,19 +466,53 @@ export class MergeEditor extends AbstractTextEditor { } this._layoutMode.value = newValue; this._ctxUsesColumnLayout.set(newValue); + this._onDidChangeSizeConstraints.fire(); } - // --- view state (TODO@bpasero revisit with https://github.com/microsoft/vscode/issues/150804) - - protected computeEditorViewState(resource: URI): undefined { - return undefined; + private _applyViewState(state: IMergeEditorViewState | undefined) { + if (!state) { + return; + } + this.inputResultView.editor.restoreViewState(state); + if (state.input1State) { + this.input1View.editor.restoreViewState(state.input1State); + } + if (state.input2State) { + this.input2View.editor.restoreViewState(state.input2State); + } + if (state.focusIndex >= 0) { + [this.input1View.editor, this.input2View.editor, this.inputResultView.editor][state.focusIndex].focus(); + } } + protected computeEditorViewState(resource: URI): IMergeEditorViewState | undefined { + if (!isEqual(this.model?.result.uri, resource)) { + // TODO@bpasero Why not check `input#resource` and don't ask me for "forgein" resources? + return undefined; + } + const result = this.inputResultView.editor.saveViewState(); + if (!result) { + return undefined; + } + const input1State = this.input1View.editor.saveViewState() ?? undefined; + const input2State = this.input2View.editor.saveViewState() ?? undefined; + const focusIndex = [this.input1View.editor, this.input2View.editor, this.inputResultView.editor].findIndex(editor => editor.hasWidgetFocus()); + return { ...result, input1State, input2State, focusIndex }; + } + + protected tracksEditorViewState(input: EditorInput): boolean { - return false; + return input instanceof MergeEditorInput; } } +type IMergeEditorViewState = ICodeEditorViewState & { + readonly input1State?: ICodeEditorViewState; + readonly input2State?: ICodeEditorViewState; + readonly focusIndex: number; +}; + + function synchronizeScrolling(scrollingEditor: CodeEditorWidget, targetEditor: CodeEditorWidget, mapping: DocumentMapping | undefined, source: MappingDirection) { if (!mapping) { return; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index ede54697d62..de910cceaee 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLast, lastOrDefault } from 'vs/base/common/arrays'; +import { findLast } from 'vs/base/common/arrays'; +import { derived, derivedObservableWithWritableCache, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; -import { elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView'; import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView'; import { ResultCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView'; @@ -26,9 +25,9 @@ export class MergeEditorViewModel { return editors.find((e) => e.isFocused.read(reader)) || lastValue; }); - private readonly manuallySetActiveModifiedBaseRange = new ObservableValue< + private readonly manuallySetActiveModifiedBaseRange = observableValue< ModifiedBaseRange | undefined - >(undefined, 'manuallySetActiveModifiedBaseRange'); + >('manuallySetActiveModifiedBaseRange', undefined); private getRange(editor: CodeEditorView, modifiedBaseRange: ModifiedBaseRange, reader: IReader | undefined): LineRange { if (editor === this.resultCodeEditorView) { @@ -39,7 +38,7 @@ export class MergeEditorViewModel { } } - public readonly activeModifiedBaseRange = derivedObservable( + public readonly activeModifiedBaseRange = derived( 'activeModifiedBaseRange', (reader) => { const focusedEditor = this.lastFocusedEditor.read(reader); @@ -78,7 +77,7 @@ export class MergeEditorViewModel { this.model.setState(baseRange, state, true, tx); } - public goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { + private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { const lastFocusedEditor = this.lastFocusedEditor.get(); if (!lastFocusedEditor) { return; @@ -92,29 +91,41 @@ export class MergeEditorViewModel { const range = this.getRange(lastFocusedEditor, modifiedBaseRange, undefined); lastFocusedEditor.editor.setPosition({ lineNumber: range.startLineNumber, - column: 1, + column: lastFocusedEditor.editor.getModel()!.getLineFirstNonWhitespaceColumn(range.startLineNumber), }); lastFocusedEditor.editor.revealLinesNearTop(range.startLineNumber, range.endLineNumberExclusive, ScrollType.Smooth); } } - public goToNextConflict(): void { + public goToNextModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => this.model.modifiedBaseRanges .get() - .find((r) => this.getRange(e, r, undefined).startLineNumber > l) || - elementAtOrUndefined(this.model.modifiedBaseRanges.get(), 0) + .find( + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).startLineNumber > l + ) || + this.model.modifiedBaseRanges + .get() + .find((r) => !onlyConflicting || r.isConflicting) ); } - public goToPreviousConflict(): void { + public goToPreviousModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => findLast( this.model.modifiedBaseRanges.get(), - (r) => this.getRange(e, r, undefined).endLineNumberExclusive < l - ) || lastOrDefault(this.model.modifiedBaseRanges.get()) + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).endLineNumberExclusive < l + ) || + findLast( + this.model.modifiedBaseRanges.get(), + (r) => !onlyConflicting || r.isConflicting + ) ); } @@ -124,6 +135,7 @@ export class MergeEditorViewModel { return; } transaction(tx => { + /** @description Toggle Active Conflict */ this.setState( activeModifiedBaseRange, this.model.getState(activeModifiedBaseRange).get().toggle(inputNumber), diff --git a/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts new file mode 100644 index 00000000000..9ec46266ae2 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/common/mergeEditor.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export type MergeEditorLayoutTypes = 'mixed' | 'columns'; + +export const ctxIsMergeEditor = new RawContextKey('isMergeEditor', false); +export const ctxMergeEditorLayout = new RawContextKey('mergeEditorLayout', 'mixed'); +export const ctxBaseResourceScheme = new RawContextKey('baseResourceScheme', ''); diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts new file mode 100644 index 00000000000..ebef4581ab6 --- /dev/null +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -0,0 +1,367 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { transaction } from 'vs/base/common/observable'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; +import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; + +suite('merge editor model', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('prepend line', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "line1\nline2", + "input1": "0\nline1\nline2", + "input2": "0\nline1\nline2", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: ['⟦⟧₀line1', 'line2'], + input1: ['⟦0', '⟧₀line1', 'line2'], + input2: ['⟦0', '⟧₀line1', 'line2'], + result: ['⟦⟧{conflicting}₀'], + }); + + model.toggleConflict(0, 1); + assert.deepStrictEqual( + { result: model.getResult() }, + { result: '0\nline1\nline2' } + ); + + model.toggleConflict(0, 2); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "0\n0\nline1\nline2" }) + ); + } + ); + }); + + test('empty base', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "", + "input1": "input1", + "input2": "input2", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: ['⟦⟧₀'], + input1: ['⟦input1⟧₀'], + input2: ['⟦input2⟧₀'], + result: ['⟦⟧{}₀'], + }); + + model.toggleConflict(0, 1); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "input1" }) + ); + + model.toggleConflict(0, 2); + assert.deepStrictEqual( + { result: model.getResult() }, + ({ result: "input2" }) + ); + } + ); + }); + + test('can merge word changes', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "hello", + "input1": "hallo", + "input2": "helloworld", + "result": "" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: ['⟦hello⟧₀'], + input1: ['⟦hallo⟧₀'], + input2: ['⟦helloworld⟧₀'], + result: ['⟦⟧{conflicting}₀'], + }); + + model.toggleConflict(0, 1); + model.toggleConflict(0, 2); + + assert.deepStrictEqual( + { result: model.getResult() }, + { result: 'halloworld' } + ); + } + ); + + }); + + test('can combine insertions at end of document', async () => { + await testMergeModel( + { + "languageId": "plaintext", + "base": "Zürich\nBern\nBasel\nChur\nGenf\nThun", + "input1": "Zürich\nBern\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}", + "input2": "Zürich\nBern\nBasel (FCB)\nChur\nGenf\nThun\nfunction f(a:number) {}", + "result": "Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: ['Zürich', 'Bern', '⟦Basel', '⟧₀Chur', '⟦⟧₁Genf', 'Thun⟦⟧₂'], + input1: [ + 'Zürich', + 'Bern', + '⟦⟧₀Chur', + '⟦Davos', + '⟧₁Genf', + 'Thun', + '⟦function f(b:boolean) {}⟧₂', + ], + input2: [ + 'Zürich', + 'Bern', + '⟦Basel (FCB)', + '⟧₀Chur', + '⟦⟧₁Genf', + 'Thun', + '⟦function f(a:number) {}⟧₂', + ], + result: [ + 'Zürich', + 'Bern', + '⟦Basel', + '⟧{}₀Chur', + '⟦Davos', + '⟧{1✓}₁Genf', + 'Thun⟦⟧{}₂', + ], + }); + + model.toggleConflict(2, 1); + model.toggleConflict(2, 2); + + assert.deepStrictEqual( + { result: model.getResult() }, + { + result: + 'Zürich\nBern\nBasel\nChur\nDavos\nGenf\nThun\nfunction f(b:boolean) {}\nfunction f(a:number) {}', + } + ); + } + ); + }); + + test('conflicts are reset', async () => { + await testMergeModel( + { + "languageId": "typescript", + "base": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "input1": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "input2": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "result": "import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';", + "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + input1: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';", + "⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + input2: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + result: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';", + "⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + '⟦<<<<<<< Updated upstream', + "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + '=======', + "import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';", + '>>>>>>> Stashed changes', + "⟧{conflicting}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + }); + } + ); + }); +}); + +async function testMergeModel( + options: MergeModelOptions, + fn: (model: MergeModelInterface) => void +): Promise { + const disposables = new DisposableStore(); + const modelInterface = disposables.add( + new MergeModelInterface(options, createModelServices(disposables)) + ); + await modelInterface.mergeModel.onInitialized; + await fn(modelInterface); + disposables.dispose(); +} + +interface MergeModelOptions { + languageId: string; + input1: string; + input2: string; + base: string; + result: string; +} + +function toSmallNumbersDec(value: number): string { + const smallNumbers = ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉']; + return value.toString().split('').map(c => smallNumbers[parseInt(c)]).join(''); +} + +class MergeModelInterface extends Disposable { + public readonly mergeModel: MergeEditorModel; + + constructor(options: MergeModelOptions, instantiationService: IInstantiationService) { + super(); + const input1TextModel = this._register(createTextModel(options.input1, options.languageId)); + const input2TextModel = this._register(createTextModel(options.input2, options.languageId)); + const baseTextModel = this._register(createTextModel(options.base, options.languageId)); + const resultTextModel = this._register(createTextModel(options.result, options.languageId)); + this.mergeModel = this._register(instantiationService.createInstance(MergeEditorModel, + baseTextModel, + input1TextModel, + '', + '', + '', + input2TextModel, + '', + '', + '', + resultTextModel, + { + async computeDiff(textModel1, textModel2) { + const result = EditorSimpleWorker.computeDiff(textModel1, textModel2, false, 10000); + if (!result) { + return { diffs: null }; + } + return { + diffs: EditorWorkerServiceDiffComputer.fromDiffComputationResult( + result, + textModel1, + textModel2 + ), + }; + }, + }, { + resetUnknownOnInitialization: false + } + )); + } + + getProjections(): unknown { + interface LabeledRange { + range: Range; + label: string; + } + function applyRanges(textModel: ITextModel, ranges: LabeledRange[]): void { + textModel.applyEdits(ranges.map(({ range, label }) => ({ + range: range, + text: `⟦${textModel.getValueInRange(range)}⟧${label}`, + }))); + } + const baseRanges = this.mergeModel.modifiedBaseRanges.get(); + + const baseTextModel = createTextModel(this.mergeModel.base.getValue()); + applyRanges( + baseTextModel, + baseRanges.map((r, idx) => ({ + range: r.baseRange.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const input1TextModel = createTextModel(this.mergeModel.input1.getValue()); + applyRanges( + input1TextModel, + baseRanges.map((r, idx) => ({ + range: r.input1Range.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const input2TextModel = createTextModel(this.mergeModel.input2.getValue()); + applyRanges( + input2TextModel, + baseRanges.map((r, idx) => ({ + range: r.input2Range.toRange(), + label: toSmallNumbersDec(idx), + })) + ); + + const resultTextModel = createTextModel(this.mergeModel.result.getValue()); + applyRanges( + resultTextModel, + baseRanges.map((r, idx) => ({ + range: this.mergeModel.getRangeInResult(r.baseRange).toRange(), + label: `{${this.mergeModel.getState(r).get()}}${toSmallNumbersDec(idx)}`, + })) + ); + + const result = { + base: baseTextModel.getValue(EndOfLinePreference.LF).split('\n'), + input1: input1TextModel.getValue(EndOfLinePreference.LF).split('\n'), + input2: input2TextModel.getValue(EndOfLinePreference.LF).split('\n'), + result: resultTextModel.getValue(EndOfLinePreference.LF).split('\n'), + }; + baseTextModel.dispose(); + input1TextModel.dispose(); + input2TextModel.dispose(); + resultTextModel.dispose(); + return result; + } + + toggleConflict(conflictIdx: number, inputNumber: 1 | 2): void { + const baseRange = this.mergeModel.modifiedBaseRanges.get()[conflictIdx]; + if (!baseRange) { + throw new Error(); + } + const state = this.mergeModel.getState(baseRange).get(); + transaction(tx => { + this.mergeModel.setState(baseRange, state.toggle(inputNumber), true, tx); + }); + } + + getResult(): string { + return this.mergeModel.result.getValue(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 2adc303d4e1..5074d759b06 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -31,7 +31,10 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_UP_COMMAND_ID, - title: localize('notebookActions.moveCellUp', "Move Cell Up"), + title: { + value: localize('notebookActions.moveCellUp', "Move Cell Up"), + original: 'Move Cell Up' + }, icon: icons.moveUpIcon, keybinding: { primary: KeyMod.Alt | KeyCode.UpArrow, @@ -57,7 +60,10 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.moveCellDown', "Move Cell Down"), + title: { + value: localize('notebookActions.moveCellDown', "Move Cell Down"), + original: 'Move Cell Down' + }, icon: icons.moveDownIcon, keybinding: { primary: KeyMod.Alt | KeyCode.DownArrow, @@ -83,7 +89,10 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_UP_COMMAND_ID, - title: localize('notebookActions.copyCellUp', "Copy Cell Up"), + title: { + value: localize('notebookActions.copyCellUp', "Copy Cell Up"), + original: 'Copy Cell Up' + }, keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -102,7 +111,10 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.copyCellDown', "Copy Cell Down"), + title: { + value: localize('notebookActions.copyCellDown', "Copy Cell Down"), + original: 'Copy Cell Down' + }, keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -137,7 +149,10 @@ registerAction2(class extends NotebookCellAction { super( { id: SPLIT_CELL_COMMAND_ID, - title: localize('notebookActions.splitCell', "Split Cell"), + title: { + value: localize('notebookActions.splitCell', "Split Cell"), + original: 'Split Cell' + }, menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and( @@ -212,7 +227,10 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), + title: { + value: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), + original: 'Join With Previous Cell' + }, keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyJ, @@ -238,7 +256,10 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), + title: { + value: localize('notebookActions.joinCellBelow', "Join With Next Cell"), + original: 'Join With Next Cell' + }, keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyJ, @@ -270,7 +291,10 @@ registerAction2(class ChangeCellToCodeAction extends NotebookMultiCellAction { constructor() { super({ id: CHANGE_CELL_TO_CODE_COMMAND_ID, - title: localize('notebookActions.changeCellToCode', "Change Cell to Code"), + title: { + value: localize('notebookActions.changeCellToCode', "Change Cell to Code"), + original: 'Change Cell to Code' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.KeyY, @@ -294,7 +318,10 @@ registerAction2(class ChangeCellToMarkdownAction extends NotebookMultiCellAction constructor() { super({ id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID, - title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), + title: { + value: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), + original: 'Change Cell to Markdown' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.KeyM, @@ -330,7 +357,10 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_INPUT_COMMAND_ID, - title: localize('notebookActions.collapseCellInput', "Collapse Cell Input"), + title: { + value: localize('notebookActions.collapseCellInput', "Collapse Cell Input"), + original: 'Collapse Cell Input' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -356,7 +386,10 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_INPUT_COMMAND_ID, - title: localize('notebookActions.expandCellInput', "Expand Cell Input"), + title: { + value: localize('notebookActions.expandCellInput', "Expand Cell Input"), + original: 'Expand Cell Input' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -382,7 +415,10 @@ registerAction2(class CollapseCellOutputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, - title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + title: { + value: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + original: 'Collapse Cell Output' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -404,7 +440,10 @@ registerAction2(class ExpandCellOuputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_OUTPUT_COMMAND_ID, - title: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + title: { + value: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + original: 'Expand Cell Output' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -427,7 +466,10 @@ registerAction2(class extends NotebookMultiCellAction { super({ id: TOGGLE_CELL_OUTPUTS_COMMAND_ID, precondition: NOTEBOOK_CELL_LIST_FOCUSED, - title: localize('notebookActions.toggleOutputs', "Toggle Outputs"), + title: { + value: localize('notebookActions.toggleOutputs', "Toggle Outputs"), + original: 'Toggle Outputs' + }, description: { description: localize('notebookActions.toggleOutputs', "Toggle Outputs"), args: cellExecutionArgs @@ -457,7 +499,10 @@ registerAction2(class CollapseAllCellInputsAction extends NotebookMultiCellActio constructor() { super({ id: COLLAPSE_ALL_CELL_INPUTS_COMMAND_ID, - title: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), + title: { + value: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), + original: 'Collapse All Cell Inputs' + }, f1: true, }); } @@ -471,7 +516,10 @@ registerAction2(class ExpandAllCellInputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_INPUTS_COMMAND_ID, - title: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), + title: { + value: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), + original: 'Expand All Cell Inputs' + }, f1: true }); } @@ -485,7 +533,10 @@ registerAction2(class CollapseAllCellOutputsAction extends NotebookMultiCellActi constructor() { super({ id: COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID, - title: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), + title: { + value: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), + original: 'Collapse All Cell Outputs' + }, f1: true, }); } @@ -499,7 +550,10 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID, - title: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), + title: { + value: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), + original: 'Expand All Cell Outputs' + }, f1: true }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 285dae8f94e..53ce0466795 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -243,7 +243,7 @@ registerAction2(class extends Action2 { quickPickItems.push({ id: 'installSuggested', description: suggestedExtension.displayName ?? suggestedExtension.extensionId, - label: nls.localize('installSuggestedKernel', '$({0}) Install suggested extensions', Codicon.lightbulb.id), + label: `$(${Codicon.lightbulb.id}) ` + nls.localize('installSuggestedKernel', 'Install suggested extensions'), }); } // there is no kernel, show the install from marketplace diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts index 3e1c47af18a..2165fa94cc2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts @@ -81,7 +81,10 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.gettingStarted', - title: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), + title: { + value: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), + original: 'Reset notebook getting started' + }, f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: CATEGORIES.Developer, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index e28f4f054bc..8c8a8fb1f59 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; @@ -14,10 +15,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; +import { NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; @@ -42,22 +42,27 @@ registerAction2(class FocusNextCellAction extends NotebookCellAction { when: ContextKeyExpr.and( NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.or( - ContextKeyExpr.and( - ContextKeyExpr.has(InputFocusedContextKey), - EditorContextKeys.editorTextFocus, - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), - NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), - ), - ContextKeyExpr.and( - NOTEBOOK_CELL_TYPE.isEqualTo('markup'), - NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), - NOTEBOOK_CURSOR_NAVIGATION_MODE - ) - ) + ContextKeyExpr.and( + ContextKeyExpr.has(InputFocusedContextKey), + EditorContextKeys.editorTextFocus, + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), + NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), + ), ), primary: KeyCode.DownArrow, - weight: KeybindingWeight.WorkbenchContrib, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), + ContextKeyExpr.and( + NOTEBOOK_CELL_TYPE.isEqualTo('markup'), + NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), + NOTEBOOK_CURSOR_NAVIGATION_MODE) + ), + primary: KeyCode.DownArrow, + weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown }, { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED), @@ -95,27 +100,35 @@ registerAction2(class FocusPreviousCellAction extends NotebookCellAction { super({ id: NOTEBOOK_FOCUS_PREVIOUS_EDITOR, title: localize('cursorMoveUp', 'Focus Previous Cell Editor'), - keybinding: { - when: ContextKeyExpr.and( - NOTEBOOK_EDITOR_FOCUSED, - ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), - ContextKeyExpr.or( + keybinding: [ + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), ContextKeyExpr.and( ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none'), ), + ), + primary: KeyCode.UpArrow, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, // code cell keybinding, focus inside editor: lower weight to not override suggest widget + }, + { + when: ContextKeyExpr.and( + NOTEBOOK_EDITOR_FOCUSED, + ContextKeyExpr.equals('config.notebook.navigation.allowNavigateToSurroundingCells', true), ContextKeyExpr.and( NOTEBOOK_CELL_TYPE.isEqualTo('markup'), NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.isEqualTo(false), NOTEBOOK_CURSOR_NAVIGATION_MODE ) - ) - ), - primary: KeyCode.UpArrow, - weight: KeybindingWeight.WorkbenchContrib, - }, + ), + primary: KeyCode.UpArrow, + weight: KeybindingWeight.WorkbenchContrib, // markdown keybinding, focus on list: higher weight to override list.focusDown + } + ], }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index b3af4143741..ab714f85b51 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CATEGORIES } from 'vs/workbench/common/actions'; @@ -121,7 +122,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.toggleLayoutTroubleshoot', - title: 'Toggle Notebook Layout Troubleshoot', + title: { + value: localize('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"), + original: 'Toggle Notebook Layout Troubleshoot' + }, category: CATEGORIES.Developer, f1: true }); @@ -144,7 +148,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.inspectLayout', - title: 'Inspect Notebook Layout', + title: { + value: localize('workbench.notebook.inspectLayout', "Inspect Notebook Layout"), + original: 'Inspect Notebook Layout' + }, category: CATEGORIES.Developer, f1: true }); @@ -169,7 +176,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.clearNotebookEdtitorTypeCache', - title: 'Clear Notebook Editor Cache', + title: { + value: localize('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"), + original: 'Clear Notebook Editor Cache' + }, category: CATEGORIES.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index f7b01782aa0..9a42a89b378 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -42,7 +42,8 @@ export const enum CellToolbarOrder { export const enum CellOverflowToolbarGroups { Copy = '1_copy', Insert = '2_insert', - Edit = '3_edit' + Edit = '3_edit', + Share = '4_share' } export interface INotebookActionContext { @@ -427,3 +428,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { group: CellOverflowToolbarGroups.Insert, when: NOTEBOOK_EDITOR_FOCUSED }); + +MenuRegistry.appendMenuItem(MenuId.NotebookCellTitle, { + title: localize('miShare', "Share"), + submenu: MenuId.EditorContextShare, + group: CellOverflowToolbarGroups.Share +}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 7250d2dc560..ebcdf94c782 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -469,7 +469,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction { constructor() { super({ id: DETECT_CELL_LANGUAGE, - title: localize('detectLanguage', 'Accept Detected Language for Cell'), + title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index 6a85c41a24a..454b565d71e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -224,7 +224,7 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction { MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, { command: { id: INSERT_CODE_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.menu.insertCode', "$(add) Code"), + title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"), tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell") }, order: 0, @@ -269,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { command: { id: INSERT_CODE_CELL_AT_TOP_COMMAND_ID, - title: localize('notebookActions.menu.insertCode', "$(add) Code"), + title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"), tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell") }, order: 0, @@ -299,7 +299,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, { command: { id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"), + title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"), tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell") }, order: 1, @@ -331,7 +331,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { command: { id: INSERT_MARKDOWN_CELL_AT_TOP_COMMAND_ID, - title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"), + title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"), tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell") }, order: 1, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 4f6a3e489c5..7f6ac0a645b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -21,7 +21,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.select', - title: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), + title: { + value: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), + original: 'Select between Notebook Layouts' + }, f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: NOTEBOOK_ACTIONS_CATEGORY, @@ -57,7 +60,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure', - title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + title: { + value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + original: 'Customize Notebook Layout' + }, f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -79,7 +85,10 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure.editorTitle', - title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + title: { + value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + original: 'Customize Notebook Layout' + }, f1: false, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -177,7 +186,10 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 { constructor() { super({ id: 'notebook.saveMimeTypeOrder', - title: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'), + title: { + value: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'), + original: 'Save Mimetype Display Order' + }, f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 466fd1bf565..33bf079467f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -846,9 +846,10 @@ configurationRegistry.registerConfiguration({ [NotebookSetting.showFoldingControls]: { description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."), type: 'string', - enum: ['always', 'mouseover'], + enum: ['always', 'never', 'mouseover'], enumDescriptions: [ nls.localize('showFoldingControls.always', "The folding controls are always visible."), + nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."), nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."), ], default: 'mouseover', @@ -880,7 +881,7 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'] }, [NotebookSetting.markupFontSize]: { - markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."), + markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to {0}, 120% of {1} is used.", '`0`', '`#editor.fontSize#`'), type: 'number', default: 0, tags: ['notebookLayout'] @@ -899,13 +900,13 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'] }, [NotebookSetting.outputFontSize]: { - markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."), + markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to {0}, {1} is used.", '`0`', '`#editor.fontSize#`'), type: 'number', default: 0, tags: ['notebookLayout'] }, [NotebookSetting.outputFontFamily]: { - markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."), + markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the {0} is used.", '`#editor.fontFamily#`'), type: 'string', tags: ['notebookLayout'] }, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index a4d3f6aafd7..6047d713272 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -47,7 +47,7 @@ import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/co import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, iconForeground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, toolbarHoverBackground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { EDITOR_PANE_BACKGROUND, PANEL_BORDER, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; import { CellEditState, CellFindMatchWithIndex, CellFocusMode, CellLayoutContext, CellRevealType, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IInsetRenderOutput, IModelDecorationsChangeAccessor, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorDelegate, INotebookEditorMouseEvent, INotebookEditorOptions, INotebookEditorViewState, INotebookViewCellsUpdateEvent, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; @@ -919,21 +919,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', styleController: (_suffix: string) => { return this._list; }, overrideStyles: { - listBackground: editorBackground, - listActiveSelectionBackground: editorBackground, + listBackground: notebookEditorBackground, + listActiveSelectionBackground: notebookEditorBackground, listActiveSelectionForeground: foreground, - listFocusAndSelectionBackground: editorBackground, + listFocusAndSelectionBackground: notebookEditorBackground, listFocusAndSelectionForeground: foreground, - listFocusBackground: editorBackground, + listFocusBackground: notebookEditorBackground, listFocusForeground: foreground, listHoverForeground: foreground, - listHoverBackground: editorBackground, + listHoverBackground: notebookEditorBackground, listHoverOutline: focusBorder, listFocusOutline: focusBorder, - listInactiveSelectionBackground: editorBackground, + listInactiveSelectionBackground: notebookEditorBackground, listInactiveSelectionForeground: foreground, - listInactiveFocusBackground: editorBackground, - listInactiveFocusOutline: editorBackground, + listInactiveFocusBackground: notebookEditorBackground, + listInactiveFocusOutline: notebookEditorBackground, }, accessibilityProvider: { getAriaLabel: (element) => { @@ -1153,22 +1153,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const endTime = Date.now() + deadline.timeRemaining(); const execute = () => { - this._backgroundMarkdownRenderRunning = false; - if (this._isDisposed) { - return; - } + try { + this._backgroundMarkdownRenderRunning = true; + if (this._isDisposed) { + return; + } - if (!this.viewModel) { - return; - } + if (!this.viewModel) { + return; + } - const firstMarkupCell = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Markup && !this._webview?.markupPreviewMapping.has(cell.id)) as MarkupCellViewModel | undefined; - if (!firstMarkupCell) { - return; - } + const firstMarkupCell = this.viewModel.viewCells.find(cell => cell.cellKind === CellKind.Markup && !this._webview?.markupPreviewMapping.has(cell.id) && !this.cellIsHidden(cell)) as MarkupCellViewModel | undefined; + if (!firstMarkupCell) { + return; + } - this._backgroundMarkdownRenderRunning = true; - this.createMarkupPreview(firstMarkupCell); + this.createMarkupPreview(firstMarkupCell); + } finally { + this._backgroundMarkdownRenderRunning = false; + } if (Date.now() < endTime) { setTimeout0(execute); @@ -2564,10 +2567,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return; } - const modelIndex = this.viewModel.getCellIndex(cell); - const foldedRanges = this.viewModel.getHiddenRanges(); - const isVisible = !foldedRanges.some(range => modelIndex >= range.start && modelIndex < range.end); - if (!isVisible) { + if (this.cellIsHidden(cell)) { return; } @@ -2585,6 +2585,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }); } + private cellIsHidden(cell: ICellViewModel): boolean { + const modelIndex = this.viewModel!.getCellIndex(cell); + const foldedRanges = this.viewModel!.getHiddenRanges(); + return foldedRanges.some(range => modelIndex >= range.start && modelIndex <= range.end); + } + async unhideMarkupPreviews(cells: readonly MarkupCellViewModel[]) { if (!this._webview) { return; @@ -3157,6 +3163,13 @@ export const cellEditorBackground = registerColor('notebook.cellEditorBackground hcLight: null }, nls.localize('notebook.cellEditorBackground', "Cell editor background color.")); +export const notebookEditorBackground = registerColor('notebook.editorBackground', { + light: EDITOR_PANE_BACKGROUND, + dark: EDITOR_PANE_BACKGROUND, + hcDark: null, + hcLight: null +}, nls.localize('notebook.editorBackground', "Notebook background color.")); + registerThemingParticipant((theme, collector) => { // add css variable rules @@ -3234,6 +3247,12 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container .action-item { background-color: ${notebookBackground} }`); } + const notebookBackgroundColor = theme.getColor(notebookEditorBackground); + + if (notebookBackgroundColor) { + collector.addRule(`.monaco-workbench .notebookOverlay.notebook-editor { background-color: ${notebookBackgroundColor}; }`); + } + const editorBackgroundColor = theme.getColor(cellEditorBackground) ?? theme.getColor(editorBackground); if (editorBackgroundColor) { collector.addRule(`.notebookOverlay .cell .monaco-editor-background, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index e8b61a303d0..44d8f209cbe 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -39,7 +39,7 @@ export class FoldedCellHint extends CellPart { const foldHintTop = element.layoutInfo.previewHeight; this._container.style.top = `${foldHintTop}px`; - } else if (element.foldingState === CellFoldingState.Expanded) { + } else { DOM.hide(this._container); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 951fbe0e2e2..a024bbe93a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -17,7 +17,7 @@ import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRange import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack'; import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { WorkspaceTextEdit } from 'vs/editor/common/languages'; +import { IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -924,14 +924,15 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return; } - const textEdits: WorkspaceTextEdit[] = []; + const textEdits: IWorkspaceTextEdit[] = []; this._lastNotebookEditResource.push(matches[0].cell.uri); matches.forEach(match => { match.matches.forEach((singleMatch, index) => { if ((singleMatch as OutputFindMatch).index === undefined) { textEdits.push({ - edit: { range: (singleMatch as FindMatch).range, text: texts[index] }, + versionId: undefined, + textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] }, resource: match.cell.uri }); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d0ccff06ccd..a95ecc6fb84 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -17,7 +17,7 @@ import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Command } from 'vs/editor/common/languages'; +import { Command, WorkspaceEditMetadata } from 'vs/editor/common/languages'; import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -497,6 +497,14 @@ export interface ICellMoveEdit { export type IImmediateCellEditOperation = ICellOutputEditByHandle | ICellPartialMetadataEditByHandle | ICellOutputItemEdit | ICellPartialInternalMetadataEdit | ICellPartialInternalMetadataEditByHandle | ICellPartialMetadataEdit; export type ICellEditOperation = IImmediateCellEditOperation | ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellPartialMetadataEdit | ICellPartialInternalMetadataEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit | ICellLanguageEdit; + +export interface IWorkspaceNotebookCellEdit { + metadata?: WorkspaceEditMetadata; + resource: URI; + notebookVersionId: number | undefined; + cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit; +} + export interface NotebookData { readonly cells: ICellDto2[]; readonly metadata: NotebookDocumentMetadata; diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 426871f84fa..2aa4ca14cf8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -59,7 +59,7 @@ export interface NotebookLayoutConfiguration { globalToolbar: boolean; consolidatedOutputButton: boolean; consolidatedRunButton: boolean; - showFoldingControls: 'always' | 'mouseover'; + showFoldingControls: 'always' | 'never' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; outputFontSize: number; @@ -385,7 +385,7 @@ export class NotebookOptions extends Disposable { } private _computeShowFoldingControlsOption() { - return this.configurationService.getValue<'always' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover'; + return this.configurationService.getValue<'always' | 'never' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover'; } private _computeFocusIndicatorOption() { diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index c13ec6b5e0c..e9f97c01b29 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -48,7 +48,7 @@ class OutputChannel extends Disposable implements IOutputChannel { } update(mode: OutputChannelUpdateMode, till?: number): void { - this.model.update(mode, till); + this.model.update(mode, till, true); } clear(): void { diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 4c01e67797c..58f8374650b 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -26,7 +26,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out export interface IOutputChannelModel extends IDisposable { readonly onDispose: Event; append(output: string): void; - update(mode: OutputChannelUpdateMode, till?: number): void; + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void; loadModel(): Promise; clear(): void; replace(value: string): void; @@ -129,12 +129,12 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } clear(): void { - this.update(OutputChannelUpdateMode.Clear, this.endOffset); + this.update(OutputChannelUpdateMode.Clear, this.endOffset, true); } - update(mode: OutputChannelUpdateMode, till?: number): void { + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); - loadModelPromise.then(() => this.doUpdate(mode, till)); + loadModelPromise.then(() => this.doUpdate(mode, till, immediate)); } loadModel(): Promise { @@ -174,7 +174,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel return this.model; } - private doUpdate(mode: OutputChannelUpdateMode, till?: number): void { + private doUpdate(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) { this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; this.cancelModelUpdate(); @@ -198,7 +198,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } else { - this.appendContent(this.model, token); + this.appendContent(this.model, immediate, token); } } @@ -206,7 +206,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString('')); } - private async appendContent(model: ITextModel, token: CancellationToken): Promise { + private async appendContent(model: ITextModel, immediate: boolean, token: CancellationToken): Promise { this.appendThrottler.trigger(async () => { /* Abort if operation is cancelled */ if (token.isCancellationRequested) { @@ -234,7 +234,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel const lastLineMaxColumn = model.getLineMaxColumn(lastLine); const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())]; this.doUpdateModel(model, edits, contentToAppend); - }); + }, immediate ? 0 : undefined); } private async replaceContent(model: ITextModel, token: CancellationToken): Promise { @@ -298,10 +298,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (!this.modelUpdateInProgress) { if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed - this.update(OutputChannelUpdateMode.Clear, 0); + this.update(OutputChannelUpdateMode.Clear, 0, true); } } - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, false /* Not needed to update immediately. Wait to collect more changes and update. */); } } @@ -340,13 +340,13 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu override append(message: string): void { this.write(message); - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, this.isVisible()); } override replace(message: string): void { const till = this._offset; this.write(message); - this.update(OutputChannelUpdateMode.Replace, till); + this.update(OutputChannelUpdateMode.Replace, till, true); } private write(content: string): void { @@ -391,8 +391,8 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output)); } - update(mode: OutputChannelUpdateMode, till?: number): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till)); + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till, immediate)); } loadModel(): Promise { diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index ff06cba0a11..11f242b2646 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -351,6 +351,12 @@ font-style: italic; } +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides.with-custom-hover:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover { + text-decoration: underline; +} + .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .codicon { vertical-align: middle; padding-left: 1px; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 3412eac14fc..83d1addd26d 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -907,7 +907,8 @@ export class SettingsEditor2 extends EditorPane { })); this._register(this.settingsTree.onDidFocus(() => { - if (document.activeElement?.classList.contains('monaco-list')) { + const classList = document.activeElement?.classList; + if (classList && classList.contains('monaco-list') && classList.contains('settings-editor-tree')) { this._currentFocusContext = SettingsFocusContext.SettingTree; this.settingRowFocused.set(true); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index 44bb7b7afd8..ae71ad571f6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -37,6 +37,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private scopeOverridesElement: HTMLElement; private scopeOverridesLabel: SimpleIconLabel; private syncIgnoredElement: HTMLElement; + private syncIgnoredHover: ICustomHover | undefined; private defaultOverrideIndicatorElement: HTMLElement; private hoverDelegate: IHoverDelegate; private hover: ICustomHover | undefined; @@ -50,19 +51,20 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.indicatorsContainerElement = DOM.append(container, $('.misc-label')); this.indicatorsContainerElement.style.display = 'inline'; + this.hoverDelegate = { + showHover: (options: IHoverDelegateOptions, focus?: boolean) => { + return hoverService.showHover(options, focus); + }, + onDidHideHover: () => { }, + delay: configurationService.getValue('workbench.hover.delay'), + placement: 'element' + }; + const scopeOverridesIndicator = this.createScopeOverridesIndicator(); this.scopeOverridesElement = scopeOverridesIndicator.element; this.scopeOverridesLabel = scopeOverridesIndicator.label; this.syncIgnoredElement = this.createSyncIgnoredElement(); this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator(); - - this.hoverDelegate = { - showHover: (options: IHoverDelegateOptions, focus?: boolean) => { - return hoverService.showHover(options, focus); - }, - delay: configurationService.getValue('workbench.hover.delay'), - placement: 'element' - }; } private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } { @@ -74,16 +76,16 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { private createSyncIgnoredElement(): HTMLElement { const syncIgnoredElement = $('span.setting-item-ignored'); const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement); - syncIgnoredLabel.text = '$(info) ' + localize('extensionSyncIgnoredLabel', 'Not synced'); + syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced'); const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync"); - setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); + this.syncIgnoredHover = setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent); return syncIgnoredElement; } private createDefaultOverrideIndicator(): HTMLElement { const defaultOverrideIndicator = $('span.setting-item-default-overridden'); const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator); - defaultOverrideLabel.text = '$(info) ' + localize('defaultOverriddenLabel', "Default value changed"); + defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed"); return defaultOverrideIndicator; } @@ -125,6 +127,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { dispose() { this.hover?.dispose(); + this.syncIgnoredHover?.dispose(); } updateScopeOverrides(element: SettingsTreeSettingElement, elementDisposables: DisposableStore, onDidClickOverrideElement: Emitter) { @@ -136,6 +139,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // Render inline if we have the flag and there are scope overrides to render, // or if there is only one scope override to render and no language overrides. this.scopeOverridesElement.style.display = 'inline'; + this.scopeOverridesElement.classList.remove('with-custom-hover'); this.hover?.dispose(); // Just show all the text in the label. @@ -167,8 +171,8 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // show the text in a custom hover only if // the feature flag isn't on. this.scopeOverridesElement.style.display = 'inline'; - let scopeOverridesLabelText = '$(info) '; - scopeOverridesLabelText += element.isConfigured ? + this.scopeOverridesElement.classList.add('with-custom-hover'); + const scopeOverridesLabelText = element.isConfigured ? localize('alsoConfiguredElsewhere', "Also modified elsewhere") : localize('configuredElsewhere', "Modified elsewhere"); this.scopeOverridesLabel.text = scopeOverridesLabelText; @@ -290,21 +294,21 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement, // Add sync ignored text const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), configurationService); if (ignoredSettings.includes(element.setting.key)) { - ariaLabelSections.push(localize('syncIgnoredTitle', "This setting is ignored during sync")); + ariaLabelSections.push(localize('syncIgnoredAriaLabel', "Setting ignored during sync")); } // Add default override indicator text const sourceToDisplay = getDefaultValueSourceToDisplay(element); if (sourceToDisplay !== undefined) { - ariaLabelSections.push(localize('defaultOverriddenDetails', "Default setting value overridden by {0}", sourceToDisplay)); + ariaLabelSections.push(localize('defaultOverriddenDetailsAriaLabel', "{0} overrides the default value", sourceToDisplay)); } // Add text about default values being overridden in other languages - const otherLanguageOverridesStart = localize('defaultOverriddenListPreface', "The default value of the setting has also been overridden for the following languages:"); const otherLanguageOverridesList = element.overriddenDefaultsLanguageList .map(language => languageService.getLanguageName(language)).join(', '); if (element.overriddenDefaultsLanguageList.length) { - ariaLabelSections.push(`${otherLanguageOverridesStart} ${otherLanguageOverridesList}`); + const otherLanguageOverridesText = localize('defaultOverriddenLanguagesList', "Language-specific default values exist for {0}", otherLanguageOverridesList); + ariaLabelSections.push(otherLanguageOverridesText); } const ariaLabel = ariaLabelSections.join('. '); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 148c6c8d379..1680881aeb1 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { debug?: { console?: { wordWrap?: boolean } }; editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } } }; - window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean } } }; + window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean } }; workbench?: { experimental?: { settingsProfiles?: { enabled?: boolean } } }; } @@ -34,6 +34,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private titleBarStyle: 'native' | 'custom' | undefined; private windowControlsOverlayEnabled: boolean | undefined; + private windowSandboxEnabled: boolean | undefined; private nativeTabs: boolean | undefined; private nativeFullScreen: boolean | undefined; private clickThroughInactive: boolean | undefined; @@ -66,11 +67,16 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } // Windows: Window Controls Overlay - if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window?.experimental?.windowControlsOverlay?.enabled !== this.windowControlsOverlayEnabled) { + if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window.experimental.windowControlsOverlay.enabled !== this.windowControlsOverlayEnabled) { this.windowControlsOverlayEnabled = config.window.experimental.windowControlsOverlay.enabled; changed = true; } + // Windows: Sandbox + if (typeof config.window?.experimental?.useSandbox === 'boolean' && config.window.experimental.useSandbox !== this.windowSandboxEnabled) { + this.windowSandboxEnabled = config.window.experimental.useSandbox; + changed = true; + } // macOS: Native tabs if (isMacintosh && typeof config.window?.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index b6488780711..0b2e94b01bc 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -353,7 +353,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'remote.autoForwardPortsSource': { type: 'string', - markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when `remote.autoForwardPorts` is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect."), + markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'), enum: ['process', 'output'], enumDescriptions: [ localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."), @@ -463,7 +463,7 @@ Registry.as(ConfigurationExtensions.Configuration) } }, defaultSnippets: [{ body: { onAutoForward: 'ignore' } }], - markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"), + markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting {0}. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", '`#remote.portsAttributes#`'), additionalProperties: false }, 'remote.localPortHost': { diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 198babcb503..efe65fa662e 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -173,6 +173,10 @@ background: transparent !important; } +.scm-view .monaco-list .monaco-list-row.cursor-default { + cursor: default; +} + .scm-view.show-actions .scm-provider > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index df9e433c2fa..41cce292993 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -108,6 +108,8 @@ class ActionButtonRenderer implements ICompressibleTreeRenderer(); + constructor( @ICommandService private commandService: ICommandService, @IContextMenuService private contextMenuService: IContextMenuService, @@ -119,8 +121,8 @@ class ActionButtonRenderer implements ICompressibleTreeRenderer, index: number, templateData: ActionButtonTemplate, height: number | undefined): void { templateData.disposable.dispose(); + const disposables = new DisposableStore(); + const actionButton = node.element; templateData.actionButton.setButton(node.element.button); + + // Remember action button + this.actionButtons.set(actionButton, templateData.actionButton); + disposables.add({ dispose: () => this.actionButtons.delete(actionButton) }); + + templateData.disposable = disposables; } renderCompressedElements(): void { throw new Error('Should never happen since node is incompressible'); } + focusActionButton(actionButton: ISCMActionButton): void { + this.actionButtons.get(actionButton)?.focus(); + } + disposeElement(node: ITreeNode, index: number, template: ActionButtonTemplate): void { template.disposable.dispose(); } @@ -2200,6 +2214,7 @@ export class SCMViewPane extends ViewPane { get viewModel(): ViewModel { return this._viewModel; } private listLabels!: ResourceLabels; private inputRenderer!: InputRenderer; + private actionButtonRenderer!: ActionButtonRenderer; private readonly disposables = new DisposableStore(); constructor( @@ -2254,6 +2269,8 @@ export class SCMViewPane extends ViewPane { this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height)); const delegate = new ListDelegate(this.inputRenderer); + this.actionButtonRenderer = this.instantiationService.createInstance(ActionButtonRenderer); + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(this.listLabels); @@ -2264,7 +2281,7 @@ export class SCMViewPane extends ViewPane { const renderers: ICompressibleTreeRenderer[] = [ this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), this.inputRenderer, - this.instantiationService.createInstance(ActionButtonRenderer), + this.actionButtonRenderer, this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), this._register(this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner)) ]; @@ -2383,6 +2400,7 @@ export class SCMViewPane extends ViewPane { if (widget) { widget.focus(); + this.tree.setFocus([], e.browserEvent); const selection = this.tree.getSelection(); @@ -2395,6 +2413,13 @@ export class SCMViewPane extends ViewPane { } else if (isSCMActionButton(e.element)) { this.scmViewService.focus(e.element.repository); + // Focus the action button + const target = e.browserEvent?.target as HTMLElement; + if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) { + this.actionButtonRenderer.focusActionButton(e.element); + this.tree.setFocus([], e.browserEvent); + } + return; } @@ -2629,11 +2654,13 @@ export class SCMActionButton implements IDisposable { if (button.secondaryCommands?.length) { const actions: IAction[] = []; - for (const commandGroup of button.secondaryCommands) { - for (const command of commandGroup) { + for (let index = 0; index < button.secondaryCommands.length; index++) { + for (const command of button.secondaryCommands[index]) { actions.push(new Action(command.id, command.title, undefined, true, async () => await executeButtonAction(command.id, ...(command.arguments || [])))); } - actions.push(new Separator()); + if (index !== button.secondaryCommands.length - 1) { + actions.push(new Separator()); + } } // ButtonWithDropdown @@ -2661,6 +2688,10 @@ export class SCMActionButton implements IDisposable { this.disposables.value!.add(attachButtonStyler(this.button, this.themeService)); } + focus(): void { + this.button?.focus(); + } + private clear(): void { this.disposables.value = new DisposableStore(); this.button = undefined; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 77782e09059..54450eb71c4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -365,7 +365,10 @@ registerAction2(class CancelSearchAction extends Action2 { constructor() { super({ id: 'search.action.cancel', - title: nls.localize('CancelSearchAction.label', "Cancel Search"), + title: { + value: nls.localize('CancelSearchAction.label', "Cancel Search"), + original: 'Cancel Search' + }, icon: searchStopIcon, category, f1: true, @@ -392,7 +395,10 @@ registerAction2(class RefreshAction extends Action2 { constructor() { super({ id: 'search.action.refreshSearchResults', - title: nls.localize('RefreshAction.label', "Refresh"), + title: { + value: nls.localize('RefreshAction.label', "Refresh"), + original: 'Refresh' + }, icon: searchRefreshIcon, precondition: Constants.ViewHasSearchPatternKey, category, @@ -414,7 +420,10 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { constructor() { super({ id: 'search.action.collapseSearchResults', - title: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + title: { + value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + original: 'Collapse All' + }, category, icon: searchCollapseAllIcon, f1: true, @@ -436,7 +445,10 @@ registerAction2(class ExpandAllAction extends Action2 { constructor() { super({ id: 'search.action.expandSearchResults', - title: nls.localize('ExpandAllAction.label', "Expand All"), + title: { + value: nls.localize('ExpandAllAction.label', "Expand All"), + original: 'Expand All' + }, category, icon: searchExpandAllIcon, f1: true, @@ -458,7 +470,10 @@ registerAction2(class ClearSearchResultsAction extends Action2 { constructor() { super({ id: 'search.action.clearSearchResults', - title: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + title: { + value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + original: 'Clear Search Results' + }, category, icon: searchClearIcon, f1: true, @@ -981,7 +996,7 @@ configurationRegistry.registerConfiguration({ 'search.searchOnTypeDebouncePeriod': { type: 'number', default: 300, - markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") + markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When {0} is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when {0} is disabled.", '`#search.searchOnType#`') }, 'search.searchEditor.doubleClickBehaviour': { type: 'string', diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index eb1497201da..784296b70de 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { parse as jsonParse, getNodeType } from 'vs/base/common/json'; -import { forEach } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { extname, basename } from 'vs/base/common/path'; import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; @@ -256,17 +255,15 @@ export class SnippetFile { this._loadPromise = Promise.resolve(this._load()).then(content => { const data = jsonParse(content); if (getNodeType(data) === 'object') { - forEach(data, entry => { - const { key: name, value: scopeOrTemplate } = entry; + for (const [name, scopeOrTemplate] of Object.entries(data)) { if (isJsonSerializedSnippet(scopeOrTemplate)) { this._parseSnippet(name, scopeOrTemplate, this.data); } else { - forEach(scopeOrTemplate, entry => { - const { key: name, value: template } = entry; + for (const [name, template] of Object.entries(scopeOrTemplate)) { this._parseSnippet(name, template, this.data); - }); + } } - }); + } } return this; }); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 366ed64536b..abc007e863d 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -358,7 +358,12 @@ class SnippetsService implements ISnippetsService { await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, disposables); }; this._disposables.add(disposables); - this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(() => this._pendingWork.push(updateUserSnippets()))); + this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(e => e.join((async () => { + if (e.preserveData) { + await this._fileService.copy(e.previous.snippetsHome, e.profile.snippetsHome); + } + this._pendingWork.push(updateUserSnippets()); + })()))); await updateUserSnippets(); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b421bbe2913..1a3261a3335 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -53,7 +53,9 @@ import { ITaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskSorter, ITaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, KeyedTaskIdentifier as KeyedTaskIdentifier, TaskDefinition, RuntimeType, - USER_TASKS_GROUP_KEY + USER_TASKS_GROUP_KEY, + TaskSettingId, + TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; @@ -1082,7 +1084,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }).then((value) => { if (runSource === TaskRunSource.User) { this.getWorkspaceTasks().then(workspaceTasks => { - RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, workspaceTasks); + RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, this._configurationService, workspaceTasks); }); } return value; @@ -1093,7 +1095,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private _isProvideTasksEnabled(): boolean { - const settingValue = this._configurationService.getValue('task.autoDetect'); + const settingValue = this._configurationService.getValue(TaskSettingId.AutoDetect); return settingValue === 'on'; } @@ -1588,7 +1590,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer { identifier: id, dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }), - name: id, + name: id } ); return { task, resolver }; @@ -1688,7 +1690,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer Prompt = 'prompt' } - const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue('task.saveBeforeRun'); + const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue(TaskSettingId.SaveBeforeRun); if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) { return false; @@ -3272,27 +3274,58 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._runConfigureTasks(); return; } + const entries: QuickPickInput[] = []; let selectedTask: Task | undefined; - let selectedEntry: ITaskQuickPickEntry; - for (const task of tasks) { - const taskGroup: TaskGroup | undefined = TaskGroup.from(task.configurationProperties.group); - if (taskGroup && taskGroup.isDefault && taskGroup._id === TaskGroup.Build._id) { - selectedTask = task; - break; - } - } - if (selectedTask) { - selectedEntry = { - label: nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task', selectedTask.getQualifiedLabel()), - task: selectedTask, - detail: this._showDetail() ? selectedTask.configurationProperties.detail : undefined - }; - } + let selectedEntry: TaskQuickPickEntryType | undefined; this._showIgnoredFoldersMessage().then(() => { - this._showQuickPick(tasks, - nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry). + for (const task of tasks) { + const taskGroup: TaskGroup | undefined = TaskGroup.from(task.configurationProperties.group); + if (taskGroup && taskGroup.isDefault && taskGroup._id === TaskGroup.Build._id) { + const label = nls.localize('TaskService.defaultBuildTaskExists', '{0} is already marked as the default build task', TaskQuickPick.getTaskLabelWithIcon(task, task.getQualifiedLabel())); + selectedTask = task; + selectedEntry = { label, task, description: this.getTaskDescription(task), detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + TaskQuickPick.applyColorStyles(task, selectedEntry, this._themeService); + } else { + const entry = { label: TaskQuickPick.getTaskLabelWithIcon(task), task, description: this.getTaskDescription(task), detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + TaskQuickPick.applyColorStyles(task, entry, this._themeService); + entries.push(entry); + } + } + if (selectedEntry) { + entries.unshift(selectedEntry); + } + const tokenSource = new CancellationTokenSource(); + const cancellationToken: CancellationToken = tokenSource.token; + this._quickInputService.pick(entries, + { placeHolder: nls.localize('TaskService.pickTask', 'Select a task to configure') }, cancellationToken). + then(async (entry) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await entries)[0]; + if ((task).task) { + entry = task; + } + } + const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; + if ((task === undefined) || (task === null)) { + return; + } + if (task === selectedTask && CustomTask.is(task)) { + this.openConfig(task); + } + if (!InMemoryTask.is(task)) { + this.customize(task, { group: { kind: 'build', isDefault: true } }, true).then(() => { + if (selectedTask && (task !== selectedTask) && !InMemoryTask.is(selectedTask)) { + this.customize(selectedTask, { group: 'build' }, false); + } + }); + } + }); + this._quickInputService.pick(entries, { + placeHolder: nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task') + }). then((entry) => { - const task: Task | undefined | null = entry ? entry.task : undefined; + const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; if ((task === undefined) || (task === null)) { return; } @@ -3492,11 +3525,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const configTasks: (TaskConfig.ICustomTask | TaskConfig.IConfiguringTask)[] = []; - const suppressTaskName = !!this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri }); + const suppressTaskName = !!this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri }); const globalConfig = { - windows: this._configurationService.getValue('tasks.windows', { resource: folder.uri }), - osx: this._configurationService.getValue('tasks.osx', { resource: folder.uri }), - linux: this._configurationService.getValue('tasks.linux', { resource: folder.uri }) + windows: this._configurationService.getValue(TasksSchemaProperties.Windows, { resource: folder.uri }), + osx: this._configurationService.getValue(TasksSchemaProperties.Osx, { resource: folder.uri }), + linux: this._configurationService.getValue(TasksSchemaProperties.Linux, { resource: folder.uri }) }; tasks.get(folder).forEach(task => { const configTask = this._upgradeTask(task, suppressTaskName, globalConfig); @@ -3508,14 +3541,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._workspaceTasksPromise = undefined; await this._writeConfiguration(folder, 'tasks.tasks', configTasks); await this._writeConfiguration(folder, 'tasks.version', '2.0.0'); - if (this._configurationService.getValue('tasks.showOutput', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.showOutput', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.ShowOutput, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.ShowOutput, undefined, { resource: folder.uri }); } - if (this._configurationService.getValue('tasks.isShellCommand', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.isShellCommand', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.IsShellCommand, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.IsShellCommand, undefined, { resource: folder.uri }); } - if (this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.suppressTaskName', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.SuppressTaskName, undefined, { resource: folder.uri }); } } this._updateSetup(); diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 3d91091af90..aa0d0441b79 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -8,7 +8,6 @@ import * as resources from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITaskService, IWorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; -import { forEach } from 'vs/base/common/collections'; import { RunOnOptions, Task, TaskRunSource, TaskSource, TaskSourceKind, TASKS_CATEGORY, WorkspaceFileTaskSource, IWorkspaceTaskSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -16,18 +15,19 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; -const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic'; +const HAS_PROMPTED_FOR_AUTOMATIC_TASKS = 'task.hasPromptedForAutomaticTasks'; +const ALLOW_AUTOMATIC_TASKS = 'task.allowAutomaticTasks'; export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution { constructor( @ITaskService private readonly _taskService: ITaskService, - @IStorageService private readonly _storageService: IStorageService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService private readonly _logService: ILogService) { super(); @@ -43,7 +43,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.'); - const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined); + const isFolderAutomaticAllowed = this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) !== 'off'; await this._workspaceTrustManagementService.workspaceTrustInitialized; const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted(); // Only run if allowed. Prompting for permission occurs when a user first tries to run a task. @@ -106,22 +106,22 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut }); } if (resultElement.configurations) { - forEach(resultElement.configurations.byIdentifier, (configedTask) => { - if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) { + for (const configuredTask of Object.values(resultElement.configurations.byIdentifier)) { + if (configuredTask.runOptions.runOn === RunOnOptions.folderOpen) { tasks.push(new Promise(resolve => { - taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => resolve(task)); + taskService.getTask(resultElement.workspaceFolder, configuredTask._id, true).then(task => resolve(task)); })); - if (configedTask.value._label) { - taskNames.push(configedTask.value._label); + if (configuredTask._label) { + taskNames.push(configuredTask._label); } else { - taskNames.push(configedTask.value.configures.task); + taskNames.push(configuredTask.configures.task); } - const location = RunAutomaticTasks._getTaskSource(configedTask.value._source); + const location = RunAutomaticTasks._getTaskSource(configuredTask._source); if (location) { locations.set(location.fsPath, location); } } - }); + } } }); } @@ -129,30 +129,33 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService, - openerService: IOpenerService, workspaceTaskResult: Map) { + openerService: IOpenerService, configurationService: IConfigurationService, workspaceTaskResult: Map) { const isWorkspaceTrusted = workspaceTrustManagementService.isWorkspaceTrusted; if (!isWorkspaceTrusted) { return; } - - const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined); - if (isFolderAutomaticAllowed !== undefined) { + if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') { return; } + const hasShownPromptForAutomaticTasks = storageService.getBoolean(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, StorageScope.WORKSPACE, undefined); const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult); if (taskNames.length > 0) { - // We have automatic tasks, prompt to allow. - this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => { - if (allow) { - RunAutomaticTasks._runTasks(taskService, tasks); - } - }); + if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'on') { + RunAutomaticTasks._runTasks(taskService, tasks); + } else if (!hasShownPromptForAutomaticTasks) { + // We have automatic tasks, prompt to allow. + this._showPrompt(notificationService, storageService, openerService, configurationService, taskNames, locations).then(allow => { + if (allow) { + RunAutomaticTasks._runTasks(taskService, tasks); + } + }); + } } } - private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService, - openerService: IOpenerService, taskNames: Array, locations: Map): Promise { + private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, + openerService: IOpenerService, configurationService: IConfigurationService, taskNames: Array, locations: Map): Promise { return new Promise(resolve => { notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic', "This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?", @@ -163,14 +166,15 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut label: nls.localize('allow', "Allow and run"), run: () => { resolve(true); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, true, ConfigurationTarget.WORKSPACE); } }, { label: nls.localize('disallow', "Disallow"), run: () => { resolve(false); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, false, ConfigurationTarget.WORKSPACE); + } }, { @@ -183,9 +187,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } }] ); + storageService.store(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); }); } - } export class ManageAutomaticTaskRunning extends Action2 { @@ -203,14 +207,13 @@ export class ManageAutomaticTaskRunning extends Action2 { public async run(accessor: ServicesAccessor): Promise { const quickInputService = accessor.get(IQuickInputService); - const storageService = accessor.get(IStorageService); + const configurationService = accessor.get(IConfigurationService); const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") }; const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") }; const value = await quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); if (!value) { return; } - - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, value === allowItem, ConfigurationTarget.WORKSPACE); } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index c9bdcd49adb..00c85294b7b 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output'; -import { ITaskEvent, TaskEventKind, TaskGroup, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -431,7 +431,7 @@ configurationRegistry.registerConfiguration({ title: nls.localize('tasksConfigurationTitle', "Tasks"), type: 'object', properties: { - 'task.problemMatchers.neverPrompt': { + [TaskSettingId.ProblemMatchersNeverPrompt]: { markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."), 'oneOf': [ { @@ -453,13 +453,13 @@ configurationRegistry.registerConfiguration({ ], default: false }, - 'task.autoDetect': { + [TaskSettingId.AutoDetect]: { markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions may also provide settings that disable auto detection."), type: 'string', enum: ['on', 'off'], default: 'on' }, - 'task.slowProviderWarning': { + [TaskSettingId.SlowProviderWarning]: { markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"), 'oneOf': [ { @@ -476,27 +476,44 @@ configurationRegistry.registerConfiguration({ ], default: true }, - 'task.quickOpen.history': { + [TaskSettingId.QuickOpenHistory]: { markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."), type: 'number', default: 30, minimum: 0, maximum: 30 }, - 'task.quickOpen.detail': { + [TaskSettingId.QuickOpenDetail]: { markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for tasks that have a detail in task quick picks, such as Run Task."), type: 'boolean', default: true }, - 'task.quickOpen.skip': { + [TaskSettingId.QuickOpenSkip]: { type: 'boolean', description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."), default: false }, - 'task.quickOpen.showAll': { + [TaskSettingId.QuickOpenShowAll]: { type: 'boolean', description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."), default: false }, - 'task.saveBeforeRun': { + [TaskSettingId.AllowAutomaticTasks]: { + type: 'string', + enum: ['on', 'auto', 'off'], + enumDescriptions: [ + nls.localize('ttask.allowAutomaticTasks.on', "Always"), + nls.localize('task.allowAutomaticTasks.auto', "Prompt for permission for each folder"), + nls.localize('task.allowAutomaticTasks.off', "Never"), + ], + description: nls.localize('task.allowAutomaticTasks', "Enable automatic tasks in the folder."), + default: 'auto', + restricted: true + }, + [TaskSettingId.ShowDecorations]: { + type: 'boolean', + description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."), + default: true + }, + [TaskSettingId.SaveBeforeRun]: { markdownDescription: nls.localize( 'task.saveBeforeRun', 'Save all dirty editors before running a task.' diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index f7d122e7e15..a2cc123e381 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -24,7 +24,6 @@ import { TaskQuickPickEntryType } from 'vs/workbench/contrib/tasks/browser/abstr export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; - export function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder { return 'uri' in folder; } @@ -64,7 +63,7 @@ export class TaskQuickPick extends Disposable { private _guessTaskLabel(task: Task | ConfiguringTask): string { if (task._label) { - return TaskQuickPick.getTaskLabelWithIcon(task); + return task._label; } if (ConfiguringTask.is(task)) { let label: string = task.configures.type; @@ -77,12 +76,13 @@ export class TaskQuickPick extends Disposable { return ''; } - public static getTaskLabelWithIcon(task: Task | ConfiguringTask): string { + public static getTaskLabelWithIcon(task: Task | ConfiguringTask, labelGuess?: string): string { + const label = labelGuess || task._label; const icon = task.configurationProperties.icon; if (!icon) { - return `${task._label}`; + return `${label}`; } - return icon.id ? `$(${icon.id}) ${task._label}` : `$(${Codicon.tools.id}) ${task._label}`; + return icon.id ? `$(${icon.id}) ${label}` : `$(${Codicon.tools.id}) ${label}`; } public static applyColorStyles(task: Task | ConfiguringTask, entry: TaskQuickPickEntryType | ITaskTwoLevelQuickPickEntry, themeService: IThemeService): void { @@ -95,7 +95,7 @@ export class TaskQuickPick extends Disposable { } private _createTaskEntry(task: Task | ConfiguringTask, extraButtons: IQuickInputButton[] = []): ITaskTwoLevelQuickPickEntry { - const entry: ITaskTwoLevelQuickPickEntry = { label: this._guessTaskLabel(task), description: this._taskService.getTaskDescription(task), task, detail: this._showDetail() ? task.configurationProperties.detail : undefined }; + const entry: ITaskTwoLevelQuickPickEntry = { label: TaskQuickPick.getTaskLabelWithIcon(task, this._guessTaskLabel(task)), description: this._taskService.getTaskDescription(task), task, detail: this._showDetail() ? task.configurationProperties.detail : undefined }; entry.buttons = []; entry.buttons.push({ iconClass: ThemeIcon.asClassName(configureTaskIcon), tooltip: nls.localize('configureTask', "Configure Task") }); entry.buttons.push(...extraButtons); @@ -107,7 +107,9 @@ export class TaskQuickPick extends Disposable { groupLabel: string, extraButtons: IQuickInputButton[] = []) { entries.push({ type: 'separator', label: groupLabel }); tasks.forEach(task => { - entries.push(this._createTaskEntry(task, extraButtons)); + if (!task.configurationProperties.hide) { + entries.push(this._createTaskEntry(task, extraButtons)); + } }); } @@ -303,7 +305,7 @@ export class TaskQuickPick extends Disposable { private async _doPickerSecondLevel(picker: IQuickPick, type: string) { picker.busy = true; if (type === SHOW_ALL) { - const items = (await this._taskService.tasks()).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); + const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); items.push(...TaskQuickPick.allSettingEntries(this._configurationService)); picker.items = items; } else { @@ -352,9 +354,13 @@ export class TaskQuickPick extends Disposable { private async _getEntriesForProvider(type: string): Promise[]> { const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b)); - let taskQuickPickEntries: QuickPickInput[]; + let taskQuickPickEntries: QuickPickInput[] = []; if (tasks.length > 0) { - taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task)); + for (const task of tasks) { + if (!task.configurationProperties.hide) { + taskQuickPickEntries.push(this._createTaskEntry(task)); + } + } taskQuickPickEntries.push({ type: 'separator' }, { diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index a2d5cc1d2a7..47d1f2a6486 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -59,7 +59,7 @@ export class TaskTerminalStatus extends Disposable { }); problemMatcher.onDidFindErrors(() => { if (this._marker) { - terminal.addGenericMark(this._marker, { hoverMessage: nls.localize('task.watchFirstError', "First error"), disableCommandStorage: true }); + terminal.addGenericMark(this._marker, { hoverMessage: nls.localize('task.watchFirstError', "Beginning of detected errors for this run"), disableCommandStorage: true }); } }); problemMatcher.onDidRequestInvalidateLastMarker(() => { @@ -85,7 +85,13 @@ export class TaskTerminalStatus extends Disposable { terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { - terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); + if (terminalData.task.configurationProperties.isBackground) { + for (const status of terminalData.terminal.statusList.statuses) { + terminalData.terminal.statusList.remove(status); + } + } else { + terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); + } } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 6d56f58a027..754a84163c1 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -31,7 +31,7 @@ import { IOutputService } from 'vs/workbench/services/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent + TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, @@ -56,9 +56,6 @@ import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; import { Codicon } from 'vs/base/common/codicons'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences'; -const taskShellIntegrationStartSequence = VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, VSCodeOscProperty.Task) + VSCodeSequence(VSCodeOscPt.CommandStart); -const taskShellIntegrationOutputSequence = VSCodeSequence(VSCodeOscPt.CommandExecuted); - interface ITerminalData { terminal: ITerminalInstance; lastTask: string; @@ -211,6 +208,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private readonly _onDidStateChange: Emitter; + get taskShellIntegrationStartSequence(): string { + return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : ''; + } + get taskShellIntegrationOutputSequence(): string { + return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : ''; + } + constructor( private _terminalService: ITerminalService, private _terminalGroupService: ITerminalGroupService, @@ -512,7 +516,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { for (const dependency of task.configurationProperties.dependsOn) { const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { - dependencyTask.configurationProperties.icon = task.configurationProperties.icon; + this._adoptConfigurationForDependencyTask(dependencyTask, task); const key = dependencyTask.getMapKey(); let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; if (!promise) { @@ -581,6 +585,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); } + private _adoptConfigurationForDependencyTask(dependencyTask: Task, task: Task): void { + if (dependencyTask.configurationProperties.icon) { + dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id; + dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color; + } else { + dependencyTask.configurationProperties.icon = task.configurationProperties.icon; + } + + if (dependencyTask.configurationProperties.hide) { + dependencyTask.configurationProperties.hide ||= task.configurationProperties.hide; + } else { + dependencyTask.configurationProperties.hide = task.configurationProperties.hide; + } + } + private async _getDependencyPromise(task: IActiveTerminalData): Promise { if (!task.task.configurationProperties.isBackground) { return task.promise; @@ -1037,7 +1056,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { remoteAuthority: this._environmentService.remoteAuthority }); let icon: URI | ThemeIcon | { light: URI; dark: URI } | undefined; - if (task.configurationProperties.icon) { + if (task.configurationProperties.icon?.id) { icon = ThemeIcon.fromId(task.configurationProperties.icon.id); } else { const taskGroupKind = task.configurationProperties.group ? GroupKind.to(task.configurationProperties.group) : undefined; @@ -1127,18 +1146,18 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.args = windowsShellArgs ? combinedShellArgs.join(' ') : combinedShellArgs; if (task.command.presentation && task.command.presentation.echo) { if (needsFolderQualification && workspaceFolder) { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', workspaceFolder.name, commandLine), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', workspaceFolder.name, commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executing', comment: ['The task command line or label'] - }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; } } else { const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined; @@ -1167,18 +1186,18 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return args.join(' '); }; if (needsFolderQualification && workspaceFolder) { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] - }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task in folder {0}: {1}', workspaceFolder.name, `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + formatMessageForTerminal(nls.localize({ key: 'task.executing', comment: ['The task command line or label'] - }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + taskShellIntegrationOutputSequence; + }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = taskShellIntegrationStartSequence + taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; } } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index d455def1f6e..fa67f8ecf65 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -45,6 +45,13 @@ const shellCommand: IJSONSchema = { deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.') }; + +const hide: IJSONSchema = { + type: 'boolean', + description: nls.localize('JsonSchema.hide', 'Hide this task from the run task quick pick'), + default: true +}; + const taskIdentifier: IJSONSchema = { type: 'object', additionalProperties: true, @@ -97,15 +104,16 @@ const detail: IJSONSchema = { const icon: IJSONSchema = { type: 'object', + description: nls.localize('JsonSchema.tasks.icon', 'An optional icon for the task'), properties: { id: { - description: nls.localize('JsonSchema.tasks.icon.id', 'An optional icon for the task'), - type: 'string', + description: nls.localize('JsonSchema.tasks.icon.id', 'An optional codicon ID to use'), + type: ['string', 'null'], enum: Array.from(Codicon.getAll(), icon => icon.id), markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), }, color: { - description: nls.localize('JsonSchema.tasks.icon.color', 'An optional color to use for the task icon'), + description: nls.localize('JsonSchema.tasks.icon.color', 'An optional color of the icon'), type: ['string', 'null'], enum: [ 'terminal.ansiBlack', @@ -406,6 +414,7 @@ const taskConfiguration: IJSONSchema = { }, presentation: Objects.deepClone(presentation), icon: Objects.deepClone(icon), + hide: Objects.deepClone(hide), options: options, problemMatcher: { $ref: '#/definitions/problemMatcherType', @@ -478,6 +487,7 @@ taskDescriptionProperties.command = Objects.deepClone(command); taskDescriptionProperties.args = Objects.deepClone(args); taskDescriptionProperties.isShellCommand = Objects.deepClone(shellCommand); taskDescriptionProperties.dependsOn = dependsOn; +taskDescriptionProperties.hide = Objects.deepClone(hide); taskDescriptionProperties.dependsOrder = dependsOrder; taskDescriptionProperties.identifier = Objects.deepClone(identifier); taskDescriptionProperties.type = Objects.deepClone(taskType); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index ed7b1403249..0bb00f57620 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -362,6 +362,11 @@ export interface IConfigurationProperties { * The icon's color in the terminal tabs list */ color?: string; + + /** + * Do not show this task in the run task quickpick + */ + hide?: boolean; } export interface ICustomTask extends ICommandProperties, IConfigurationProperties { @@ -1322,7 +1327,8 @@ namespace ConfigurationProperties { { property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' }, { property: 'options' }, - { property: 'icon' } + { property: 'icon' }, + { property: 'hide' } ]; export function from(this: void, external: IConfigurationProperties & { [key: string]: any }, context: IParseContext, @@ -1350,7 +1356,7 @@ namespace ConfigurationProperties { result.identifier = external.identifier; } result.icon = external.icon; - + result.hide = external.hide; if (external.isBackground !== undefined) { result.isBackground = !!external.isBackground; } @@ -1483,7 +1489,7 @@ namespace ConfiguringTask { type, taskIdentifier, RunOptions.fromConfiguration(external.runOptions), - {} + { hide: external.hide } ); const configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties); result.addTaskLoadMessages(configuration.errors); @@ -1635,7 +1641,8 @@ namespace CustomTask { { name: configuredProps.configurationProperties.name || contributedTask.configurationProperties.name, identifier: configuredProps.configurationProperties.identifier || contributedTask.configurationProperties.identifier, - icon: contributedTask.configurationProperties.icon + icon: configuredProps.configurationProperties.icon, + hide: configuredProps.configurationProperties.hide }, ); @@ -2119,7 +2126,7 @@ class ConfigurationParser { identifier: name, group: Tasks.TaskGroup.Build, isBackground: isBackground, - problemMatchers: matchers, + problemMatchers: matchers } ); const taskGroupKind = GroupKind.from(fileConfig.group); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1f366dfb6d9..5877beb6437 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -548,7 +548,12 @@ export interface IConfigurationProperties { /** * The icon for this task in the terminal tabs list */ - icon?: { id: string; color?: string }; + icon?: { id?: string; color?: string }; + + /** + * Do not show this task in the run task quickpick + */ + hide?: boolean; } export enum RunOnOptions { @@ -912,7 +917,12 @@ export class ContributedTask extends CommonTask { /** * The icon for the task */ - icon: { id: string; color?: string } | undefined; + icon: { id?: string; color?: string } | undefined; + + /** + * Don't show the task in the run task quickpick + */ + hide?: boolean; public constructor(id: string, source: IExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier, command: ICommandConfiguration, hasDefinedMatchers: boolean, runOptions: IRunOptions, @@ -922,6 +932,7 @@ export class ContributedTask extends CommonTask { this.hasDefinedMatchers = hasDefinedMatchers; this.command = command; this.icon = configurationProperties.icon; + this.hide = configurationProperties.hide; } public override clone(): ContributedTask { @@ -1179,6 +1190,30 @@ export namespace KeyedTaskIdentifier { } } +export const enum TaskSettingId { + AutoDetect = 'task.autoDetect', + SaveBeforeRun = 'task.saveBeforeRun', + ShowDecorations = 'task.showDecorations', + ProblemMatchersNeverPrompt = 'task.problemMatchers.neverPrompt', + SlowProviderWarning = 'task.slowProviderWarning', + QuickOpenHistory = 'task.quickOpen.history', + QuickOpenDetail = 'task.quickOpen.detail', + QuickOpenSkip = 'task.quickOpen.skip', + QuickOpenShowAll = 'task.quickOpen.showAll', + AllowAutomaticTasks = 'task.allowAutomaticTasks' +} + +export const enum TasksSchemaProperties { + Tasks = 'tasks', + SuppressTaskName = 'tasks.suppressTaskName', + Windows = 'tasks.windows', + Osx = 'tasks.osx', + Linux = 'tasks.linux', + ShowOutput = 'tasks.showOutput', + IsShellCommand = 'tasks.isShellCommand', + ServiceTestSetting = 'tasks.service.testSetting', +} + export namespace TaskDefinition { export function createTaskIdentifier(external: ITaskIdentifier, reporter: { error(message: string): void }): KeyedTaskIdentifier | undefined { const definition = TaskDefinitionRegistry.get(external.type); diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index cbbf65c9c47..45b60a78489 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -70,7 +70,7 @@ suite('Task Terminal Status', () => { taskService.triggerStateChange({ kind: TaskEventKind.End, exitCode: 2 }); await poll(async () => Promise.resolve(), () => testTerminal?.statusList.primary?.id === FAILED_TASK_STATUS.id, 'terminal status should be updated'); }); - test('Should add active status when a non-background task is run for a second time in the same terminal', async () => { + test('Should add active status when a non-background task is run for a second time in the same terminal', () => { taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted }); assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); @@ -81,6 +81,24 @@ suite('Task Terminal Status', () => { taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); }); + test('Should drop status when a background task exits', async () => { + taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted, runType: TaskRunType.Background }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessEnded, exitCode: 0 }); + await poll(async () => Promise.resolve(), () => testTerminal?.statusList.statuses?.includes(SUCCEEDED_TASK_STATUS) === false, 'terminal should have dropped status'); + }); + test('Should add succeeded status when a non-background task exits', () => { + taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted, runType: TaskRunType.SingleRun }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessEnded, exitCode: 0 }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + }); }); function assertStatus(actual: ITerminalStatusList, expected: ITerminalStatus): void { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 42b01b307bf..28d0c22794f 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -57,6 +57,8 @@ export class TerminalLinkManager extends DisposableStore { // both local and remote terminals are present private readonly _resolvedLinkCache = new LinkCache(); + private _lastTopLine: number | undefined; + constructor( private readonly _xterm: Terminal, private readonly _processManager: ITerminalProcessManager, @@ -141,12 +143,20 @@ export class TerminalLinkManager extends DisposableStore { return links[0]; } - async getLinks(): Promise { + async getLinks(extended?: boolean): Promise { const wordResults: ILink[] = []; const webResults: ILink[] = []; const fileResults: ILink[] = []; - - for (let i = this._xterm.buffer.active.length - 1; i >= this._xterm.buffer.active.viewportY; i--) { + let noMoreResults: boolean = false; + let topLine = !extended ? this._xterm.buffer.active.viewportY - Math.min(this._xterm.rows, 50) : this._lastTopLine! - 1000; + if (topLine < 0 || topLine - Math.min(this._xterm.rows, 50) < 0) { + noMoreResults = true; + } + if (topLine < 0) { + topLine = 0; + } + this._lastTopLine = topLine; + for (let i = this._xterm.buffer.active.length - 1; i >= topLine; i--) { const links = await this._getLinksForLine(i); if (links) { const { wordLinks, webLinks, fileLinks } = links; @@ -161,7 +171,7 @@ export class TerminalLinkManager extends DisposableStore { } } } - return { webLinks: webResults, fileLinks: fileResults, wordLinks: wordResults }; + return { webLinks: webResults, fileLinks: fileResults, wordLinks: wordResults, noMoreResults }; } private async _getLinksForLine(y: number): Promise { @@ -469,6 +479,7 @@ export interface IDetectedLinks { wordLinks?: ILink[]; webLinks?: ILink[]; fileLinks?: ILink[]; + noMoreResults?: boolean; } const enum LinkCacheConstants { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index bfc044ba133..6fc5f4eb043 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -189,9 +189,23 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { // Try open as an absolute link let resourceMatch: IResourceMatch | undefined; if (absolutePath) { - const slashNormalizedPath = this._os === OperatingSystem.Windows ? absolutePath.replace(/\\/g, '/') : absolutePath; - const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const uri = URI.from({ scheme, path: slashNormalizedPath }); + let normalizedAbsolutePath: string = absolutePath; + if (this._os === OperatingSystem.Windows) { + normalizedAbsolutePath = absolutePath.replace(/\\/g, '/'); + if (normalizedAbsolutePath.match(/[a-z]:/i)) { + normalizedAbsolutePath = `/${normalizedAbsolutePath}`; + } + } + let uri: URI; + if (this._workbenchEnvironmentService.remoteAuthority) { + uri = URI.from({ + scheme: Schemas.vscodeRemote, + authority: this._workbenchEnvironmentService.remoteAuthority, + path: normalizedAbsolutePath + }); + } else { + uri = URI.file(normalizedAbsolutePath); + } try { const fileStat = await this._fileService.stat(uri); resourceMatch = { uri, isDirectory: fileStat.isDirectory }; @@ -218,6 +232,16 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise { const sanitizedLink = text.replace(/:\d+(:\d+)?$/, ''); + // For links made up of only a file name (no folder), disallow exact link matching. For + // example searching for `foo.txt` when there is no cwd information available (ie. only the + // initial cwd) should NOT search as it's ambiguous if there are multiple matches. + // + // However, for a link like `src/foo.txt`, if there's an exact match for `src/foo.txt` in + // any folder we want to take it, even if there are partial matches like `src2/foo.txt` + // available. + if (!sanitizedLink.match(/[\\/]/)) { + return false; + } try { const result = await this._getExactMatch(sanitizedLink); if (result) { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts index 1a2b18727b5..3a44a394e62 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkQuickpick.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { EventType } from 'vs/base/browser/dom'; +import { Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IDetectedLinks } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; @@ -11,6 +12,9 @@ import { TerminalLinkQuickPickEvent } from 'vs/workbench/contrib/terminal/browse import { ILink } from 'xterm'; export class TerminalLinkQuickpick { + + private readonly _onDidRequestMoreLinks = new Emitter(); + readonly onDidRequestMoreLinks = this._onDidRequestMoreLinks.event; constructor( @IQuickInputService private readonly _quickInputService: IQuickInputService ) { } @@ -21,7 +25,8 @@ export class TerminalLinkQuickpick { const webPicks = links.webLinks ? await this._generatePicks(links.webLinks) : undefined; const options = { placeHolder: localize('terminal.integrated.openDetectedLink', "Select the link to open"), - canPickMany: false + canPickMany: false, + }; const picks: LinkQuickPickItem[] = []; if (webPicks) { @@ -36,13 +41,21 @@ export class TerminalLinkQuickpick { picks.push({ type: 'separator', label: localize('terminal.integrated.searchLinks', "Workspace Search") }); picks.push(...wordPicks); } - + picks.push({ type: 'separator' }); + if (!links.noMoreResults) { + const showMoreItem = { label: localize('terminal.integrated.showMoreLinks', "Show more links") }; + picks.push(showMoreItem); + } const pick = await this._quickInputService.pick(picks, options); if (!pick) { return; } const event = new TerminalLinkQuickPickEvent(EventType.CLICK); - pick.link.activate(event, pick.label); + if ('link' in pick) { + pick.link.activate(event, pick.label); + } else { + this._onDidRequestMoreLinks.fire(); + } return; } @@ -67,4 +80,4 @@ export interface ITerminalLinkQuickPickItem extends IQuickPickItem { link: ILink; } -type LinkQuickPickItem = ITerminalLinkQuickPickItem | IQuickPickSeparator; +type LinkQuickPickItem = ITerminalLinkQuickPickItem | IQuickPickSeparator | IQuickPickItem; diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index 48e5c0659bc..cec80cf90b1 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -49,10 +49,11 @@ export const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathC /** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160, replacing space with nonBreakningSpace or space ASCII code - 32. */ export const lineAndColumnClause = [ + '(([^:\\s\\(\\)<>\'\"\\[\\]]*) ((\\d+))(:(\\d+)))', // (file path) 336:9 [see #140780] '((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 - '((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 + '((\\S*):\\s?line ((\\d+)(, col(umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] '(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9 ].join('|').replace(/ /g, `[${'\u00A0'} ]`); diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index b0c525a298d..88498fbfb9a 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -39,13 +39,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then builtin return fi -# Disable shell integration if HISTCONTROL is set to erase duplicate entries as the exit code -# reporting relies on the duplicates existing -if [[ "$HISTCONTROL" =~ .*erasedups.* ]]; then - builtin unset VSCODE_SHELL_INTEGRATION - builtin return -fi - if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then builtin return fi @@ -56,7 +49,7 @@ __vsc_original_PS2="$PS2" __vsc_custom_PS1="" __vsc_custom_PS2="" __vsc_in_command_execution="1" -__vsc_last_history_id=$(history 1 | awk '{print $1;}') +__vsc_current_command="" __vsc_prompt_start() { builtin printf "\033]633;A\007" @@ -72,6 +65,7 @@ __vsc_update_cwd() { __vsc_command_output_start() { builtin printf "\033]633;C\007" + builtin printf "\033]633;E;$__vsc_current_command\007" } __vsc_continuation_start() { @@ -83,12 +77,10 @@ __vsc_continuation_end() { } __vsc_command_complete() { - local __vsc_history_id=$(builtin history 1 | awk '{print $1;}') - if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then + if [ "$__vsc_current_command" = "" ]; then builtin printf "\033]633;D\007" else builtin printf "\033]633;D;%s\007" "$__vsc_status" - __vsc_last_history_id=$__vsc_history_id fi __vsc_update_cwd } @@ -113,6 +105,7 @@ __vsc_update_prompt() { __vsc_precmd() { __vsc_command_complete "$__vsc_status" + __vsc_current_command="" __vsc_update_prompt } @@ -120,6 +113,11 @@ __vsc_preexec() { if [ "$__vsc_in_command_execution" = "0" ]; then __vsc_initialized=1 __vsc_in_command_execution="1" + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + __vsc_current_command=$BASH_COMMAND + else + __vsc_current_command="" + fi __vsc_command_output_start fi } diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index d1d42db9451..7db2583a817 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -11,7 +11,7 @@ fi # This variable allows the shell to both detect that VS Code's shell integration is enabled as well # as disable it by unsetting the variable. -export VSCODE_SHELL_INTEGRATION=1 +VSCODE_SHELL_INTEGRATION=1 # Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet if [[ "$VSCODE_INJECTION" == "1" ]]; then @@ -21,6 +21,10 @@ if [[ "$VSCODE_INJECTION" == "1" ]]; then . $USER_ZDOTDIR/.zshrc ZDOTDIR=$VSCODE_ZDOTDIR fi + + if [[ -f $USER_ZDOTDIR/.zsh_history ]]; then + HISTFILE=$USER_ZDOTDIR/.zsh_history + fi fi # Shell integration was disabled by the shell, exit without warning assuming either the shell has @@ -29,9 +33,8 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then builtin return fi -__vsc_initialized="0" __vsc_in_command_execution="1" -__vsc_last_history_id=0 +__vsc_current_command="" __vsc_prompt_start() { builtin printf "\033]633;A\007" @@ -47,6 +50,7 @@ __vsc_update_cwd() { __vsc_command_output_start() { builtin printf "\033]633;C\007" + builtin printf "\033]633;E;$__vsc_current_command\007" } __vsc_continuation_start() { @@ -66,17 +70,10 @@ __vsc_right_prompt_end() { } __vsc_command_complete() { - builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}') - # Don't write the command complete sequence for the first prompt without an associated command - if [[ "$__vsc_initialized" == "1" ]]; then - if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then - builtin printf "\033]633;D\007" - else - builtin printf "\033]633;D;%s\007" "$__vsc_status" - __vsc_last_history_id=$__vsc_history_id - fi - else + if [[ "$__vsc_current_command" == "" ]]; then builtin printf "\033]633;D\007" + else + builtin printf "\033]633;D;%s\007" "$__vsc_status" fi __vsc_update_cwd } @@ -108,6 +105,7 @@ __vsc_precmd() { fi __vsc_command_complete "$__vsc_status" + __vsc_current_command="" # in command execution if [ -n "$__vsc_in_command_execution" ]; then @@ -121,8 +119,8 @@ __vsc_preexec() { if [ -n "$RPROMPT" ]; then RPROMPT="$__vsc_prior_rprompt" fi - __vsc_initialized="1" __vsc_in_command_execution="1" + __vsc_current_command=$1 __vsc_command_output_start } add-zsh-hook precmd __vsc_precmd diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index dbbcea77cf9..35454bcae4e 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -239,6 +239,20 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack return undefined; } + async attachToRevivedProcess(id: number): Promise { + if (!this._remoteTerminalChannel) { + throw new Error(`Cannot create remote terminal when there is no remote!`); + } + + try { + const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id) ?? id; + return await this.attachToProcess(newId); + } catch (e) { + this._logService.trace(`Couldn't attach to process ${e.message}`); + } + return undefined; + } + async listProcesses(): Promise { const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listProcesses() : []; return terms.map(termDto => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index baefdbf7a67..aa125ac3d78 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -472,6 +472,11 @@ export interface ITerminalInstance { */ readonly persistentProcessId: number | undefined; + /** + * The id of a persistent process during the shutdown process + */ + shutdownPersistentProcessId: number | undefined; + /** * Whether the process should be persisted across reloads. */ @@ -675,6 +680,12 @@ export interface ITerminalInstance { */ copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise; + + /** + * Copies the ouput of the last command + */ + copyLastCommandOutput(): Promise; + /** * Current selection in the terminal. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 7bc8ca063a0..5f5bbce74dd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -307,7 +307,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.RunRecentCommand, - title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' }, + title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command..."), original: 'Run Recent Command...' }, f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) @@ -327,11 +327,25 @@ export function registerTerminalActions() { } } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.CopyLastCommand, + title: { value: localize('workbench.action.terminal.copyLastCommand', 'Copy Last Command'), original: 'Copy Last Command' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.copyLastCommandOutput(); + } + }); registerAction2(class extends Action2 { constructor() { super({ id: TerminalCommandId.GoToRecentDirectory, - title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' }, + title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory..."), original: 'Go to Recent Directory...' }, f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) @@ -1709,6 +1723,7 @@ export function registerTerminalActions() { const themeService = accessor.get(IThemeService); const groupService = accessor.get(ITerminalGroupService); const notificationService = accessor.get(INotificationService); + const picks: ITerminalQuickPickItem[] = []; if (groupService.instances.length <= 1) { notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action')); @@ -1718,7 +1733,7 @@ export function registerTerminalActions() { for (const terminal of otherInstances) { const group = groupService.getGroupForInstance(terminal); if (group?.terminalInstances.length === 1) { - const iconId = getIconId(terminal); + const iconId = getIconId(accessor, terminal); const label = `$(${iconId}): ${terminal.title}`; const iconClasses: string[] = []; const colorClass = getColorClass(terminal); @@ -2112,10 +2127,11 @@ export function registerTerminalActions() { title: { value: localize('workbench.action.terminal.sizeToContentWidth', "Toggle Size to Content Width"), original: 'Toggle Size to Content Width' }, f1: true, category, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), keybinding: { primary: KeyMod.Alt | KeyCode.KeyZ, - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, + when: TerminalContextKeys.focus } }); } @@ -2123,12 +2139,13 @@ export function registerTerminalActions() { await accessor.get(ITerminalService).doWithActiveInstance(t => t.toggleSizeToContentWidth()); } }); + registerAction2(class extends Action2 { constructor() { super({ id: TerminalCommandId.SizeToContentWidthInstance, title: terminalStrings.toggleSizeToContentWidth, - f1: true, + f1: false, category, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus) }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index aec68ee5d91..74b83274dca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -279,7 +279,8 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor const inputKey = resource.path; if ('pid' in deserializedInput) { - const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: deserializedInput }, TerminalLocation.Editor); + const newDeserializedInput = { ...deserializedInput, findRevivedId: true }; + const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor); instance.target = TerminalLocation.Editor; const input = this._instantiationService.createInstance(TerminalEditorInput, resource, instance); this._registerInstance(inputKey, input, instance); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts b/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts index 4582bfb0c82..7d873ca534f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEscapeSequences.ts @@ -97,8 +97,7 @@ export const enum VSCodeOscProperty { */ export const enum ITermOscPt { /** - * Set a mark on the scroll bar `OSC 1337 ; SetMark` - * Based on ITerm's `OSC 1337 ; SetMark` + * Based on ITerm's `OSC 1337 ; SetMark` sets a mark on the scrollbar */ SetMark = 'SetMark' } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index e94818dd002..d311e6feb1c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -15,7 +15,17 @@ import { IShellLaunchConfig, ITerminalTabLayoutInfoById } from 'vs/platform/term import { TerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { getPartByLocation } from 'vs/workbench/browser/parts/views/viewsService'; -const SPLIT_PANE_MIN_SIZE = 120; +const enum Constants { + /** + * The minimum size in pixels of a split pane. + */ + SplitPaneMinSize = 120, + /** + * The number of cells the terminal gets added or removed when asked to increase or decrease + * the view size. + */ + ResizePartCellCount = 4 +} class SplitPaneContainer extends Disposable { private _height: number; @@ -91,10 +101,10 @@ class SplitPaneContainer extends Disposable { } // Ensure the size is not reduced beyond the minimum, otherwise weird things can happen - if (sizes[index] + amount < SPLIT_PANE_MIN_SIZE) { - amount = SPLIT_PANE_MIN_SIZE - sizes[index]; - } else if (sizes[indexToChange] - amount < SPLIT_PANE_MIN_SIZE) { - amount = sizes[indexToChange] - SPLIT_PANE_MIN_SIZE; + if (sizes[index] + amount < Constants.SplitPaneMinSize) { + amount = Constants.SplitPaneMinSize - sizes[index]; + } else if (sizes[indexToChange] - amount < Constants.SplitPaneMinSize) { + amount = sizes[indexToChange] - Constants.SplitPaneMinSize; } // Apply the size change @@ -207,7 +217,7 @@ class SplitPaneContainer extends Disposable { } class SplitPane implements IView { - minimumSize: number = SPLIT_PANE_MIN_SIZE; + minimumSize: number = Constants.SplitPaneMinSize; maximumSize: number = Number.MAX_VALUE; orientation: Orientation | undefined; @@ -560,9 +570,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { const isHorizontal = (direction === Direction.Left || direction === Direction.Right); const font = this._terminalService.configHelper.getFont(); // TODO: Support letter spacing and line height - const amount = isHorizontal ? font.charWidth : font.charHeight; - if (amount) { - this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, amount, getPartByLocation(this._terminalLocation)); + const charSize = (isHorizontal ? font.charWidth : font.charHeight); + if (charSize) { + this._splitPaneContainer.resizePane(this._activeInstanceIndex, direction, charSize * Constants.ResizePartCellCount, getPartByLocation(this._terminalLocation)); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index b098cc3ece1..89acbedfa80 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from 'vs/base/common/codicons'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -116,9 +117,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr return iconClasses; } -export function getIconId(terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string { +export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string { if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) { - return Codicon.terminal.id; + return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id; } return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 695a359ef0e..de3acb2313b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -85,6 +85,8 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; const enum Constants { /** @@ -154,6 +156,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _processManager: ITerminalProcessManager; private readonly _resource: URI; + private _shutdownPersistentProcessId: number | undefined; // Enables disposal of the xterm onKey // event when the CwdDetection capability @@ -380,7 +383,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IWorkspaceTrustRequestService private readonly _workspaceTrustRequestService: IWorkspaceTrustRequestService, @IHistoryService private readonly _historyService: IHistoryService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @ICommandService private readonly _commandService: ICommandService ) { super(); @@ -547,7 +551,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _getIcon(): TerminalIcon | undefined { if (!this._icon) { - this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined; + this._icon = this._processManager.processState >= ProcessState.Launching + ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) + : undefined; } return this._icon; } @@ -664,8 +670,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return TerminalInstance._lastKnownCanvasDimensions; } - get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; } - get shouldPersist(): boolean { return this._processManager.shouldPersist && !this.shellLaunchConfig.isTransient; } + set shutdownPersistentProcessId(shutdownPersistentProcessId: number | undefined) { + this._shutdownPersistentProcessId = shutdownPersistentProcessId; + } + get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId ?? this._shutdownPersistentProcessId; } + get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient; } /** * Create xterm.js instance and attach data listeners. @@ -780,25 +789,28 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - async showLinkQuickpick(): Promise { + async showLinkQuickpick(extended?: boolean): Promise { if (!this._terminalLinkQuickpick) { this._terminalLinkQuickpick = this._instantiationService.createInstance(TerminalLinkQuickpick); + this._terminalLinkQuickpick.onDidRequestMoreLinks(() => { + this.showLinkQuickpick(true); + }); } - const links = await this._getLinks(); + const links = await this._getLinks(extended); if (!links) { return; } return await this._terminalLinkQuickpick.show(links); } - private async _getLinks(): Promise { + private async _getLinks(extended?: boolean): Promise { if (!this.areLinksReady || !this._linkManager) { throw new Error('terminal links are not ready, cannot generate link quick pick'); } if (!this.xterm) { throw new Error('no xterm'); } - return this._linkManager.getLinks(); + return this._linkManager.getLinks(extended); } async openRecentLink(type: 'localFile' | 'url'): Promise { @@ -870,7 +882,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { description, id: entry.timestamp.toString(), command: entry, - buttons: entry.hasOutput ? buttons : undefined + buttons: entry.hasOutput() ? buttons : undefined }); commandMap.add(label); } @@ -1232,6 +1244,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + async copyLastCommandOutput(): Promise { + const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands; + if (!commands || commands.length === 0) { + return; + } + const command = commands[commands.length - 1]; + if (!command?.hasOutput()) { + return; + } + const output = command.getOutput(); + if (output) { + await this._clipboardService.writeText(output); + } + } + get selection(): string | undefined { return this.xterm && this.hasSelection() ? this.xterm.raw.getSelection() : undefined; } @@ -1308,11 +1335,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } override dispose(reason?: TerminalExitReason): void { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`); dispose(this._linkManager); this._linkManager = undefined; @@ -1352,8 +1374,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // hasn't happened yet this._onProcessExit(undefined); - this._onDisposed.fire(this); - + if (!this._isDisposed) { + this._isDisposed = true; + this._onDisposed.fire(this); + } super.dispose(); } @@ -1731,13 +1755,19 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { id: TerminalStatus.ShellIntegrationAttentionNeeded, severity: Severity.Warning, icon: Codicon.warning, - tooltip: (`${exitMessage} ` ?? '') + nls.localize('launchFailed.exitCodeOnlyShellIntegration', 'Disabling shell integration with {0} might help.', '`terminal.integrated.shellIntegration.enabled`'), + tooltip: (`${exitMessage} ` ?? '') + nls.localize('launchFailed.exitCodeOnlyShellIntegration', 'Disabling shell integration in user settings might help.'), hoverActions: [{ commandId: TerminalCommandId.ShellIntegrationLearnMore, - label: nls.localize('shellIntegration.learnMore', "Learn more"), + label: nls.localize('shellIntegration.learnMore', "Learn more about shell integration"), run: () => { this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration'); } + }, { + commandId: 'workbench.action.openSettings', + label: nls.localize('shellIntegration.openSettings', "Open user settings"), + run: () => { + this._commandService.executeCommand('workbench.action.openSettings', 'terminal.integrated.shellIntegration.enabled'); + } }] }); this._telemetryService.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates the process exited when created with shell integration args' }>('terminal/shellIntegrationFailureProcessExit'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 3e2725c70ff..ba6540e654c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -292,7 +292,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } } else { if (shellLaunchConfig.attachPersistentProcess) { - const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); + const result = shellLaunchConfig.attachPersistentProcess.findRevivedId ? await backend.attachToRevivedProcess(shellLaunchConfig.attachPersistentProcess.id) : await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); if (result) { newProcess = result; } else { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index e715b730b66..e67654f2c70 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -11,7 +11,7 @@ import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import * as nls from 'vs/nls'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; @@ -22,6 +22,7 @@ type DefaultProfileName = string; export class TerminalProfileQuickpick { constructor( @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, + @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IThemeService private readonly _themeService: IThemeService @@ -155,7 +156,7 @@ export class TerminalProfileQuickpick { } } if (!icon || !getIconRegistry().getIcon(icon.id)) { - icon = Codicon.terminal; + icon = this._terminalProfileResolverService.getDefaultIcon(); } const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true); const colorClass = getColorClass(contributed); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 0849f6676b2..f920f87442f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -16,6 +16,7 @@ import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalI import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import * as path from 'vs/base/common/path'; import { Codicon } from 'vs/base/common/codicons'; +import { getIconRegistry, IIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { debounce } from 'vs/base/common/decorators'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -51,6 +52,8 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro private _primaryBackendOs: OperatingSystem | undefined; + private readonly _iconRegistry: IIconRegistry = getIconRegistry(); + private _defaultProfileName: string | undefined; get defaultProfileName(): string | undefined { return this._defaultProfileName; } @@ -94,11 +97,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void { if (shellLaunchConfig.icon) { - shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || Codicon.terminal; + shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this.getDefaultIcon(); return; } if (shellLaunchConfig.customPtyImplementation) { - shellLaunchConfig.icon = Codicon.terminal; + shellLaunchConfig.icon = this.getDefaultIcon(); return; } if (shellLaunchConfig.executable) { @@ -108,6 +111,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro if (defaultProfile) { shellLaunchConfig.icon = defaultProfile.icon; } + if (!shellLaunchConfig.icon) { + shellLaunchConfig.icon = this.getDefaultIcon(); + } + } + + getDefaultIcon(): TerminalIcon & ThemeIcon { + return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal; } async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { @@ -135,7 +145,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Verify the icon is valid, and fallback correctly to the generic terminal id if there is // an issue - shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || Codicon.terminal; + shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) + || this._getCustomIcon(resolvedProfile.icon) + || this.getDefaultIcon(); // Override the name if specified if (resolvedProfile.overrideName) { @@ -143,7 +155,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro } // Apply the color - shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color; + shellLaunchConfig.color = shellLaunchConfig.color + || resolvedProfile.color + || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor); // Resolve useShellEnvironment based on the setting if it's not set if (shellLaunchConfig.useShellEnvironment === undefined) { @@ -232,6 +246,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro if (defaultProfileName && typeof defaultProfileName === 'string') { return this._terminalProfileService.availableProfiles.find(e => e.profileName === defaultProfileName); } + return undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 60c49b4b34b..7bdc8975d82 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -67,7 +67,7 @@ export class TerminalProfileService implements ITerminalProfileService { // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual // default terminal before launching the first terminal. This isn't expected to ever take // this long. - this._profilesReadyBarrier = new AutoOpenBarrier(5000); + this._profilesReadyBarrier = new AutoOpenBarrier(20000); this.refreshAvailableProfiles(); this._setupConfigListener(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index 11af476412f..ff5fbd96df8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -16,6 +16,7 @@ import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/te import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; let terminalPicks: Array = []; export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { @@ -28,7 +29,8 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider 1 ? `${groupInfo.groupIndex + 1}.${terminalIndex + 1}` diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 0a17ca0fc8f..7ed18631566 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -21,6 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalExitReason, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -38,7 +39,6 @@ import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/wo import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -620,8 +620,13 @@ export class TerminalService implements ITerminalService { return; } + // Force dispose of all terminal instances + const shouldPersistTerminalsForEvent = this._shouldReviveProcesses(e.reason); for (const instance of this.instances) { + if (shouldPersistTerminalsForEvent) { + instance.shutdownPersistentProcessId = instance.persistentProcessId; + } instance.dispose(TerminalExitReason.Shutdown); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index d9fc17533ed..14890b1b9bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -309,7 +309,7 @@ class TerminalTabsRenderer implements IListRenderer { + this._register(this._terminal.onData(() => { this._currentMarker = Boundary.Bottom; - }); + })); } constructor( diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 6154b78f928..45b820a935c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -12,7 +12,7 @@ import { CommandInvalidationReason, ITerminalCapabilityStore, TerminalCapability import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { localize } from 'vs/nls'; @@ -289,6 +289,10 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { element.classList.add(DecorationSelector.CommandDecoration, DecorationSelector.Codicon, DecorationSelector.XtermDecoration); if (genericMarkProperties) { element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.GenericMarkerIcon); + if (!genericMarkProperties.hoverMessage) { + //disable the mouse pointer + element.classList.add(DecorationSelector.Default); + } } else if (exitCode === undefined) { element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default); element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`); @@ -317,11 +321,14 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { return; } this._hoverDelayer.trigger(() => { - let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}...`; + let hoverContent = `${localize('terminalPromptContextMenu', "Show Command Actions")}`; hoverContent += '\n\n---\n\n'; - if (command.genericMarkProperties?.hoverMessage) { - // TODO:@meganrogge localize - hoverContent = command.genericMarkProperties.hoverMessage; + if (command.genericMarkProperties) { + if (command.genericMarkProperties.hoverMessage) { + hoverContent = command.genericMarkProperties.hoverMessage; + } else { + return; + } } else if (command.exitCode) { if (command.exitCode === -1) { hoverContent += localize('terminalPromptCommandFailed', 'Command executed {0} and failed', fromNow(command.timestamp, true)); @@ -346,24 +353,39 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private async _getCommandActions(command: ITerminalCommand): Promise { const actions: IAction[] = []; - if (command.hasOutput) { + if (command.command !== '') { + const labelRun = localize("terminal.rerunCommand", 'Rerun Command'); actions.push({ - class: 'copy-output', tooltip: 'Copy Output', dispose: () => { }, id: 'terminal.copyOutput', label: localize("terminal.copyOutput", 'Copy Output'), enabled: true, + class: undefined, tooltip: labelRun, dispose: () => { }, id: 'terminal.rerunCommand', label: labelRun, enabled: true, + run: () => this._onDidRequestRunCommand.fire({ command }) + }); + const labelCopy = localize("terminal.copyCommand", 'Copy Command'); + actions.push({ + class: undefined, tooltip: labelCopy, dispose: () => { }, id: 'terminal.copyCommand', label: labelCopy, enabled: true, + run: () => this._clipboardService.writeText(command.command) + }); + } + if (command.hasOutput()) { + if (actions.length > 0) { + actions.push(new Separator()); + } + const labelText = localize("terminal.copyOutput", 'Copy Output'); + actions.push({ + class: undefined, tooltip: labelText, dispose: () => { }, id: 'terminal.copyOutput', label: labelText, enabled: true, run: () => this._clipboardService.writeText(command.getOutput()!) }); + const labelHtml = localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'); actions.push({ - class: 'copy-output', tooltip: 'Copy Output as HTML', dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'), enabled: true, + class: undefined, tooltip: labelHtml, dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: labelHtml, enabled: true, run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true }) }); } - if (command.command !== '') { - actions.push({ - class: 'rerun-command', tooltip: 'Rerun Command', dispose: () => { }, id: 'terminal.rerunCommand', label: localize("terminal.rerunCommand", 'Rerun Command'), enabled: true, - run: () => this._onDidRequestRunCommand.fire({ command }) - }); + if (actions.length > 0) { + actions.push(new Separator()); } + const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration'); actions.push({ - class: 'how-does-this-work', tooltip: 'How does this work?', dispose: () => { }, id: 'terminal.howDoesThisWork', label: localize("terminal.howDoesThisWork", 'How does this work?'), enabled: true, + class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true, run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration') }); return actions; diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index e8c7da68932..1b62d1e9cb3 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -299,6 +299,10 @@ export class RemoteTerminalChannelClient implements IPtyHostController { return this._channel.call('$reviveTerminalProcesses', [state, dateTimeFormatLocate]); } + getRevivedPtyNewId(id: number): Promise { + return this._channel.call('$getRevivedPtyNewId', [id]); + } + serializeTerminalState(ids: number[]): Promise { return this._channel.call('$serializeTerminalState', [ids]); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 791a2673e07..e935775fcd3 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { IGenericMarkProperties, IProcessDetails, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITerminalCapabilityStore, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; export const TERMINAL_VIEW_ID = 'terminal'; @@ -54,6 +55,7 @@ export interface ITerminalProfileResolverService { getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise; getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise; getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise; + getDefaultIcon(): TerminalIcon & ThemeIcon; getEnvironment(remoteAuthority: string | undefined): Promise; createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise; } @@ -116,6 +118,7 @@ export interface ITerminalBackend { onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>; attachToProcess(id: number): Promise; + attachToRevivedProcess(id: number): Promise; listProcesses(): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise; @@ -340,7 +343,7 @@ export interface ITerminalCommand { cwd?: string; exitCode?: number; marker?: IXtermMarker; - hasOutput: boolean; + hasOutput(): boolean; getOutput(): string | undefined; genericMarkProperties?: IGenericMarkProperties; } @@ -457,11 +460,6 @@ export interface IStartExtensionTerminalRequest { callback: (error: ITerminalLaunchError | undefined) => void; } -export interface IDefaultShellAndArgsRequest { - useAutomationShell: boolean; - callback: (shell: string, args: string[] | string | undefined) => void; -} - export const QUICK_LAUNCH_PROFILE_CHOICE = 'workbench.action.terminal.profile.choice'; export const enum TerminalCommandId { @@ -480,6 +478,7 @@ export const enum TerminalCommandId { OpenFileLink = 'workbench.action.terminal.openFileLink', OpenWebLink = 'workbench.action.terminal.openUrlLink', RunRecentCommand = 'workbench.action.terminal.runRecentCommand', + CopyLastCommand = 'workbench.action.terminal.copyLastCommand', GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory', CopySelection = 'workbench.action.terminal.copySelection', CopySelectionAsHtml = 'workbench.action.terminal.copySelectionAsHtml', @@ -578,6 +577,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TerminalCommandId.Clear, TerminalCommandId.CopySelection, TerminalCommandId.CopySelectionAsHtml, + TerminalCommandId.CopyLastCommand, TerminalCommandId.DeleteToLineStart, TerminalCommandId.DeleteWordLeft, TerminalCommandId.DeleteWordRight, diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index efe3aa79bd3..3c5513e7c90 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -9,6 +9,8 @@ import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAU import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; +import { Codicon } from 'vs/base/common/codicons'; +import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; const terminalDescriptors = '\n- ' + [ '`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"), @@ -34,10 +36,19 @@ const terminalConfiguration: IConfigurationNode = { type: 'object', properties: { [TerminalSettingId.SendKeybindingsToShell]: { - markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."), + markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding {0}, which can be used alternatively for fine tuning.", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: false }, + [TerminalSettingId.TabsDefaultColor]: { + description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminal icons by default."), + ...terminalColorSchema + }, + [TerminalSettingId.TabsDefaultIcon]: { + description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminal icons by default."), + ...terminalIconSchema, + default: Codicon.terminal.id, + }, [TerminalSettingId.TabsEnabled]: { description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'), type: 'boolean', @@ -106,17 +117,17 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.ShellIntegrationDecorationIconSuccess]: { type: 'string', default: 'primitive-dot', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIconError]: { type: 'string', default: 'error-small', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`.") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIcon]: { type: 'string', default: 'circle-outline', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.TabsFocusMode]: { type: 'string', @@ -139,7 +150,7 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.AltClickMovesCursor]: { - markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when `#editor.multiCursorModifier#` is set to `'alt'` (the default value). This may not work reliably depending on your shell."), + markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when {0} is set to {1} (the default value). This may not work reliably depending on your shell.", '`#editor.multiCursorModifier#`', '`\'alt\'`'), type: 'boolean', default: true }, @@ -159,7 +170,7 @@ const terminalConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.FontFamily]: { - markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), + markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to {0}'s value.", '`#editor.fontFamily#`'), type: 'string' }, // TODO: Support font ligatures @@ -254,7 +265,7 @@ const terminalConfiguration: IConfigurationNode = { default: TerminalCursorStyle.BLOCK }, [TerminalSettingId.CursorWidth]: { - markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."), + markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when {0} is set to {1}.", '`#terminal.integrated.cursorStyle#`', '`line`'), type: 'number', default: 1 }, @@ -354,7 +365,8 @@ const terminalConfiguration: IConfigurationNode = { 'terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'), - `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')` + `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`, + ), type: 'array', items: { @@ -363,7 +375,7 @@ const terminalConfiguration: IConfigurationNode = { default: [] }, [TerminalSettingId.AllowChords]: { - markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: true }, @@ -464,7 +476,7 @@ const terminalConfiguration: IConfigurationNode = { default: 30, }, [TerminalSettingId.LocalEchoEnabled]: { - markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `#terminal.integrated.localEchoLatencyThreshold#`"), + markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override {0}", '`#terminal.integrated.localEchoLatencyThreshold#`'), type: 'string', enum: ['on', 'off', 'auto'], enumDescriptions: [ @@ -536,7 +548,7 @@ const terminalConfiguration: IConfigurationNode = { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."), type: 'boolean', - default: false + default: true }, [TerminalSettingId.ShellIntegrationDecorationsEnabled]: { restricted: true, diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index b2f01c3e853..d61acd4c4a4 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -168,6 +168,16 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke return undefined; } + async attachToRevivedProcess(id: number): Promise { + try { + const newId = await this._localPtyService.getRevivedPtyNewId(id) ?? id; + return await this.attachToProcess(newId); + } catch (e) { + this._logService.trace(`Couldn't attach to process ${e.message}`); + } + return undefined; + } + async listProcesses(): Promise { return this._localPtyService.listProcesses(); } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index dcbdef59e2c..225dd72bbdc 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -97,7 +97,37 @@ suite('Workbench - TerminalLinkOpeners', () => { capabilities.add(TerminalCapability.CommandDetection, commandDetection); }); - test('should open single exact match against cwd when searching if it exists', async () => { + test('should open single exact match against cwd when searching if it exists when command detection cwd is available', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + // Set a fake detected command starting as line 0 to establish the cwd + commandDetection.setCommands([{ + command: '', + cwd: '/initial/cwd', + timestamp: 0, + getOutput() { return undefined; }, + marker: { + line: 0 + } as Partial as any, + hasOutput() { return true; } + }]); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' }) + ]); + await opener.open({ + text: 'foo/bar.txt', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///initial/cwd/foo/bar.txt', + source: 'editor' + }); + }); + + test('should open single exact match against cwd for paths containing a separator when searching if it exists, even when command detection isn\'t available', async () => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); @@ -116,6 +146,25 @@ suite('Workbench - TerminalLinkOpeners', () => { }); }); + test('should not open single exact match for paths not containing a when command detection isn\'t available', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' }) + ]); + await opener.open({ + text: 'bar.txt', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'bar.txt', + source: 'search' + }); + }); + suite('macOS/Linux', () => { setup(() => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); @@ -139,7 +188,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, - hasOutput: true + hasOutput() { return true; } }]); await opener.open({ text: 'file.txt', @@ -188,7 +237,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, - hasOutput: true + hasOutput() { return true; } }]); await opener.open({ text: 'file.txt', diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts index 8ac3607d6e8..93ff1ce3687 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts @@ -58,6 +58,8 @@ const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' }, { urlFormat: '{0}:line {1}', line: '5' }, { urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' }, + { urlFormat: '{0}: line {1}', line: '5' }, + { urlFormat: '{0}: line {1}, col {2}', line: '5', column: '3' }, { urlFormat: '{0}({1})', line: '5' }, { urlFormat: '{0} ({1})', line: '5' }, { urlFormat: '{0}({1},{2})', line: '5', column: '3' }, @@ -66,6 +68,7 @@ const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0} ({1}, {2})', line: '5', column: '3' }, { urlFormat: '{0}:{1}', line: '5' }, { urlFormat: '{0}:{1}:{2}', line: '5', column: '3' }, + { urlFormat: '{0} {1}:{2}', line: '5', column: '3' }, { urlFormat: '{0}[{1}]', line: '5' }, { urlFormat: '{0} [{1}]', line: '5' }, { urlFormat: '{0}[{1},{2}]', line: '5', column: '3' }, diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts index 80802218586..3ea312e1e1e 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts @@ -56,21 +56,21 @@ suite('DecorationAddon', () => { suite('registerDecoration', async () => { test('should throw when command has no marker', async () => { - throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: false } as ITerminalCommand)); + throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand)); }); test('should return undefined when marker has been disposed of', async () => { const marker = xterm.registerMarker(1); marker?.dispose(); - strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); test('should return undefined when command is just empty chars', async () => { const marker = xterm.registerMarker(1); marker?.dispose(); - strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); test('should return decoration when marker has not been disposed of', async () => { const marker = xterm.registerMarker(2); - notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); }); }); diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index a35b293a2a9..99bc8e0b095 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -80,7 +80,7 @@ const defaultTerminalConfig: Partial = { scrollback: 1000, fastScrollSensitivity: 2, mouseWheelScrollSensitivity: 1, - unicodeVersion: '11' + unicodeVersion: '6' }; suite('XtermTerminal', () => { diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts index 0f0d26be6b4..6a74365ede4 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts @@ -6,11 +6,11 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; +import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; -export const testIdentityProvider: IIdentityProvider = { +export const testIdentityProvider: IIdentityProvider = { getId(element) { - return element.treeId + '\0' + element.test.expand; + return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand); } }; diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 7f42e463ff6..d93803367a4 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -19,10 +19,11 @@ .test-output-peek-tree .test-peek-item .name { display: flex; align-items: center; -} - -.test-explorer .test-item .label { - white-space: pre-wrap; + flex-grow: 1; + gap: 0.15em; + width: 0; + overflow: hidden; + white-space: nowrap; } .test-explorer .test-item, diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index db687ec08b6..556f7f4e65d 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -406,7 +406,7 @@ export class CancelTestRunAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRunAction, - title: localize('testing.cancelRun', "Cancel Test Run"), + title: { value: localize('testing.cancelRun', "Cancel Test Run"), original: 'Cancel Test Run' }, icon: icons.testingCancelIcon, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -443,7 +443,7 @@ export class TestingViewAsListAction extends ViewAction { super({ id: TestCommandId.TestingViewAsListAction, viewId: Testing.ExplorerViewId, - title: localize('testing.viewAsList', "View as List"), + title: { value: localize('testing.viewAsList', "View as List"), original: 'View as List' }, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List), menu: { id: MenuId.ViewTitle, @@ -467,7 +467,7 @@ export class TestingViewAsTreeAction extends ViewAction { super({ id: TestCommandId.TestingViewAsTreeAction, viewId: Testing.ExplorerViewId, - title: localize('testing.viewAsTree', "View as Tree"), + title: { value: localize('testing.viewAsTree', "View as Tree"), original: 'View as Tree' }, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree), menu: { id: MenuId.ViewTitle, @@ -492,7 +492,7 @@ export class TestingSortByStatusAction extends ViewAction { super({ id: TestCommandId.TestingSortByStatusAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByStatus', "Sort by Status"), + title: { value: localize('testing.sortByStatus', "Sort by Status"), original: 'Sort by Status' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByStatus), menu: { id: MenuId.ViewTitle, @@ -516,7 +516,7 @@ export class TestingSortByLocationAction extends ViewAction super({ id: TestCommandId.TestingSortByLocationAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByLocation', "Sort by Location"), + title: { value: localize('testing.sortByLocation', "Sort by Location"), original: 'Sort by Location' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation), menu: { id: MenuId.ViewTitle, @@ -540,7 +540,7 @@ export class TestingSortByDurationAction extends ViewAction super({ id: TestCommandId.TestingSortByDurationAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByDuration', "Sort by Duration"), + title: { value: localize('testing.sortByDuration', "Sort by Duration"), original: 'Sort by Duration' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByDuration), menu: { id: MenuId.ViewTitle, @@ -563,7 +563,7 @@ export class ShowMostRecentOutputAction extends Action2 { constructor() { super({ id: TestCommandId.ShowMostRecentOutputAction, - title: localize('testing.showMostRecentOutput', "Show Output"), + title: { value: localize('testing.showMostRecentOutput', "Show Output"), original: 'Show Output' }, category, icon: Codicon.terminal, keybinding: { @@ -594,7 +594,7 @@ export class CollapseAllAction extends ViewAction { super({ id: TestCommandId.CollapseAllAction, viewId: Testing.ExplorerViewId, - title: localize('testing.collapseAll', "Collapse All Tests"), + title: { value: localize('testing.collapseAll', "Collapse All Tests"), original: 'Collapse All Tests' }, icon: Codicon.collapseAll, menu: { id: MenuId.ViewTitle, @@ -617,7 +617,7 @@ export class ClearTestResultsAction extends Action2 { constructor() { super({ id: TestCommandId.ClearTestResultsAction, - title: localize('testing.clearResults', "Clear All Results"), + title: { value: localize('testing.clearResults', "Clear All Results"), original: 'Clear All Results' }, category, icon: Codicon.trash, menu: [{ @@ -646,7 +646,7 @@ export class GoToTest extends Action2 { constructor() { super({ id: TestCommandId.GoToTest, - title: localize('testing.editFocusedTest', "Go to Test"), + title: { value: localize('testing.editFocusedTest', "Go to Test"), original: 'Go to Test' }, icon: Codicon.goToFile, menu: testItemInlineAndInContext(ActionOrder.GoToTest, TestingContextKeys.testItemHasUri.isEqualTo(true)), keybinding: { @@ -742,7 +742,7 @@ export class RunAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.RunAtCursor, - title: localize('testing.runAtCursor', "Run Test at Cursor"), + title: { value: localize('testing.runAtCursor', "Run Test at Cursor"), original: 'Run Test at Cursor' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -757,7 +757,7 @@ export class DebugAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.DebugAtCursor, - title: localize('testing.debugAtCursor', "Debug Test at Cursor"), + title: { value: localize('testing.debugAtCursor', "Debug Test at Cursor"), original: 'Debug Test at Cursor' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -824,7 +824,7 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.RunCurrentFile, - title: localize('testing.runCurrentFile', "Run Tests in Current File"), + title: { value: localize('testing.runCurrentFile', "Run Tests in Current File"), original: 'Run Tests in Current File' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -840,7 +840,7 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.DebugCurrentFile, - title: localize('testing.debugCurrentFile', "Debug Tests in Current File"), + title: { value: localize('testing.debugCurrentFile', "Debug Tests in Current File"), original: 'Debug Tests in Current File' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -948,7 +948,7 @@ export class ReRunFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.ReRunFailedTests, - title: localize('testing.reRunFailTests', "Rerun Failed Tests"), + title: { value: localize('testing.reRunFailTests', "Rerun Failed Tests"), original: 'Rerun Failed Tests' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -969,7 +969,7 @@ export class DebugFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.DebugFailedTests, - title: localize('testing.debugFailTests', "Debug Failed Tests"), + title: { value: localize('testing.debugFailTests', "Debug Failed Tests"), original: 'Debug Failed Tests' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -990,7 +990,7 @@ export class ReRunLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.ReRunLastRun, - title: localize('testing.reRunLastRun', "Rerun Last Run"), + title: { value: localize('testing.reRunLastRun', "Rerun Last Run"), original: 'Rerun Last Run' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1011,7 +1011,7 @@ export class DebugLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.DebugLastRun, - title: localize('testing.debugLastRun', "Debug Last Run"), + title: { value: localize('testing.debugLastRun', "Debug Last Run"), original: 'Debug Last Run' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1032,7 +1032,7 @@ export class SearchForTestExtension extends Action2 { constructor() { super({ id: TestCommandId.SearchForTestExtension, - title: localize('testing.searchForTestExtension', "Search for Test Extension"), + title: { value: localize('testing.searchForTestExtension', "Search for Test Extension"), original: 'Search for Test Extension' }, }); } @@ -1048,7 +1048,7 @@ export class OpenOutputPeek extends Action2 { constructor() { super({ id: TestCommandId.OpenOutputPeek, - title: localize('testing.openOutputPeek', "Peek Output"), + title: { value: localize('testing.openOutputPeek', "Peek Output"), original: 'Peek Output' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1070,7 +1070,7 @@ export class ToggleInlineTestOutput extends Action2 { constructor() { super({ id: TestCommandId.ToggleInlineTestOutput, - title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), + title: { value: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), original: 'Toggle Inline Test Output' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1119,7 +1119,7 @@ export class RefreshTestsAction extends Action2 { constructor() { super({ id: TestCommandId.RefreshTestsAction, - title: localize('testing.refreshTests', "Refresh Tests"), + title: { value: localize('testing.refreshTests', "Refresh Tests"), original: 'Refresh Tests' }, category, icon: icons.testingRefreshTests, keybinding: { @@ -1155,7 +1155,7 @@ export class CancelTestRefreshAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRefreshAction, - title: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), + title: { value: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), original: 'Cancel Test Refresh' }, category, icon: icons.testingCancelRefreshTests, menu: refreshMenus(true), diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 3605c75ec80..f6580294960 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -87,7 +87,6 @@ viewsRegistry.registerViews([{ name: localize('testExplorer', "Test Explorer"), ctorDescriptor: new SyncDescriptor(TestingExplorerView), canToggleVisibility: true, - workspace: true, canMoveView: true, weight: 80, order: -999, diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index 4d1af363aa7..52d57770b21 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -252,7 +252,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: TestCommandId.FilterAction, - title: localize('filter', "Filter"), + title: { value: localize('filter', "Filter"), original: 'Filter' }, }); } async run(): Promise { } diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index c3452de68dd..988ad294015 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1666,7 +1666,7 @@ export class GoToNextMessageAction extends EditorAction2 { super({ id: GoToNextMessageAction.ID, f1: true, - title: localize('testing.goToNextMessage', "Go to Next Test Failure"), + title: { value: localize('testing.goToNextMessage', "Go to Next Test Failure"), original: 'Go to Next Test Failure' }, icon: Codicon.arrowDown, category: CATEGORIES.Test, keybinding: { @@ -1696,7 +1696,7 @@ export class GoToPreviousMessageAction extends EditorAction2 { super({ id: GoToPreviousMessageAction.ID, f1: true, - title: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), + title: { value: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), original: 'Go to Previous Test Failure' }, icon: Codicon.arrowUp, category: CATEGORIES.Test, keybinding: { @@ -1726,7 +1726,7 @@ export class OpenMessageInEditorAction extends EditorAction2 { super({ id: OpenMessageInEditorAction.ID, f1: false, - title: localize('testing.openMessageInEditor', "Open in Editor"), + title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' }, icon: Codicon.linkExternal, category: CATEGORIES.Test, menu: [{ id: MenuId.TestPeekTitle }], @@ -1744,7 +1744,7 @@ export class ToggleTestingPeekHistory extends EditorAction2 { super({ id: ToggleTestingPeekHistory.ID, f1: true, - title: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), + title: { value: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), original: 'Toggle Test History in Peek' }, icon: Codicon.history, category: CATEGORIES.Test, menu: [{ diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 5d22660c689..1ff72278d33 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -34,8 +34,8 @@ class DownloadUpdateAction extends Action2 { constructor() { super({ id: 'update.downloadUpdate', - title: localize('downloadUpdate', "Download Update"), - category: product.nameShort, + title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) }); @@ -50,8 +50,8 @@ class InstallUpdateAction extends Action2 { constructor() { super({ id: 'update.installUpdate', - title: localize('installUpdate', "Install Update"), - category: product.nameShort, + title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) }); @@ -66,8 +66,8 @@ class RestartToUpdateAction extends Action2 { constructor() { super({ id: 'update.restartToUpdate', - title: localize('restartToUpdate', "Restart to Update"), - category: product.nameShort, + title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index 0dc78c3dce4..1ff40d85b54 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -163,7 +163,7 @@ export class ProductContribution implements IWorkbenchContribution { return; } - const lastVersion = parseVersion(storageService.get(ProductContribution.KEY, StorageScope.PROFILE, '')); + const lastVersion = parseVersion(storageService.get(ProductContribution.KEY, StorageScope.APPLICATION, '')); const currentVersion = parseVersion(productService.version); const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); const releaseNotesUrl = productService.releaseNotesUrl; @@ -186,7 +186,7 @@ export class ProductContribution implements IWorkbenchContribution { }); } - storageService.store(ProductContribution.KEY, productService.version, StorageScope.PROFILE, StorageTarget.MACHINE); + storageService.store(ProductContribution.KEY, productService.version, StorageScope.APPLICATION, StorageTarget.MACHINE); }); } } @@ -224,12 +224,12 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu */ const currentVersion = this.productService.commit; - const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.PROFILE); + const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.APPLICATION); // if current version != stored version, clear both fields if (currentVersion !== lastKnownVersion) { - this.storageService.remove('update/lastKnownVersion', StorageScope.PROFILE); - this.storageService.remove('update/updateNotificationTime', StorageScope.PROFILE); + this.storageService.remove('update/lastKnownVersion', StorageScope.APPLICATION); + this.storageService.remove('update/updateNotificationTime', StorageScope.APPLICATION); } this.registerGlobalActivityActions(); @@ -400,15 +400,15 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private shouldShowNotification(): boolean { const currentVersion = this.productService.commit; const currentMillis = new Date().getTime(); - const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.PROFILE); + const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.APPLICATION); // if version != stored version, save version and date if (currentVersion !== lastKnownVersion) { - this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.PROFILE, StorageTarget.MACHINE); - this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.APPLICATION, StorageTarget.MACHINE); + this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.APPLICATION, StorageTarget.MACHINE); } - const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.PROFILE, currentMillis); + const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.APPLICATION, currentMillis); const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24); return diffDays > 5; @@ -536,12 +536,12 @@ export class SwitchProductQualityContribution extends Disposable implements IWor const userDataSyncStore = userDataSyncStoreManagementService.userDataSyncStore; let userDataSyncStoreType: UserDataSyncStoreType | undefined; if (userDataSyncStore && isSwitchingToInsiders && userDataSyncEnablementService.isEnabled() - && !storageService.getBoolean(selectSettingsSyncServiceDialogShownKey, StorageScope.PROFILE, false)) { + && !storageService.getBoolean(selectSettingsSyncServiceDialogShownKey, StorageScope.APPLICATION, false)) { userDataSyncStoreType = await this.selectSettingsSyncService(dialogService); if (!userDataSyncStoreType) { return; } - storageService.store(selectSettingsSyncServiceDialogShownKey, true, StorageScope.PROFILE, StorageTarget.USER); + storageService.store(selectSettingsSyncServiceDialogShownKey, true, StorageScope.APPLICATION, StorageTarget.USER); if (userDataSyncStoreType === 'stable') { // Update the stable service type in the current window, so that it uses stable service after switched to insiders version (after reload). await userDataSyncStoreManagementService.switch(userDataSyncStoreType); @@ -576,7 +576,7 @@ export class SwitchProductQualityContribution extends Disposable implements IWor } else { // Reset if (userDataSyncStoreType) { - storageService.remove(selectSettingsSyncServiceDialogShownKey, StorageScope.PROFILE); + storageService.remove(selectSettingsSyncServiceDialogShownKey, StorageScope.APPLICATION); } } } catch (error) { diff --git a/src/vs/workbench/contrib/url/browser/trustedDomains.ts b/src/vs/workbench/contrib/url/browser/trustedDomains.ts index 44ec35f83f8..2019b04e521 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomains.ts @@ -112,11 +112,11 @@ export async function configureOpenerTrustedDomainsHandler( case 'trust': { const itemToTrust = pickedResult.toTrust; if (trustedDomains.indexOf(itemToTrust) === -1) { - storageService.remove(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, StorageScope.PROFILE); + storageService.remove(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, StorageScope.APPLICATION); storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify([...trustedDomains, itemToTrust]), - StorageScope.PROFILE, + StorageScope.APPLICATION, StorageTarget.USER ); @@ -214,7 +214,7 @@ export function readStaticTrustedDomains(accessor: ServicesAccessor): IStaticTru let trustedDomains: string[] = []; try { - const trustedDomainsSrc = storageService.get(TRUSTED_DOMAINS_STORAGE_KEY, StorageScope.PROFILE); + const trustedDomainsSrc = storageService.get(TRUSTED_DOMAINS_STORAGE_KEY, StorageScope.APPLICATION); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts index a80ff1ec236..c13d93e6721 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsFileSystemProvider.ts @@ -109,7 +109,7 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith async readFile(resource: URI): Promise { let trustedDomainsContent = this.storageService.get( TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, - StorageScope.PROFILE + StorageScope.APPLICATION ); const configuring: string | undefined = resource.fragment; @@ -134,11 +134,11 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProviderWith const trustedDomainsContent = VSBuffer.wrap(content).toString(); const trustedDomains = parse(trustedDomainsContent); - this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.PROFILE, StorageTarget.USER); + this.storageService.store(TRUSTED_DOMAINS_CONTENT_STORAGE_KEY, trustedDomainsContent, StorageScope.APPLICATION, StorageTarget.USER); this.storageService.store( TRUSTED_DOMAINS_STORAGE_KEY, JSON.stringify(trustedDomains) || '', - StorageScope.PROFILE, + StorageScope.APPLICATION, StorageTarget.USER ); } catch (err) { } diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts index c19d471fdc8..ae8e9bbaf5d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution.ts @@ -3,25 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isWeb } from 'vs/base/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { PROFILES_ENABLEMENT_CONFIG, PROFILES_ENABLEMENT_CONFIG_SCHEMA } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { UserDataProfilesWorkbenchContribution } from 'vs/workbench/contrib/userDataProfile/browser/userDataProfile'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import '../common/profileActions'; import '../common/userDataProfileActions'; -if (!isWeb) { - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - ...workbenchConfigurationNodeBase, - 'properties': { - [PROFILES_ENABLEMENT_CONFIG]: PROFILES_ENABLEMENT_CONFIG_SCHEMA - } - }); -} - const workbenchRegistry = Registry.as(Extensions.Workbench); workbenchRegistry.registerWorkbenchContribution(UserDataProfilesWorkbenchContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 5d97df3951c..44aa262be18 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -3,17 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfilesSubMenu, PROFILES_CATEGORY, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TTILE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; const CONTEXT_CURRENT_PROFILE = new RawContextKey('currentUserDataProfile', ''); @@ -28,10 +35,13 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, @IStatusbarService private readonly statusBarService: IStatusbarService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IProductService private readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); + this.registerConfiguration(); + this.currentProfileContext = CONTEXT_CURRENT_PROFILE.bindTo(contextKeyService); this.currentProfileContext.set(this.userDataProfileService.currentProfile.id); this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => this.currentProfileContext.set(this.userDataProfileService.currentProfile.id))); @@ -42,6 +52,22 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements this.registerActions(); } + private registerConfiguration(): void { + if (!isWeb && this.productService.quality !== 'stable') { + Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + [PROFILES_ENABLEMENT_CONFIG]: { + 'type': 'boolean', + 'default': false, + 'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."), + scope: ConfigurationScope.APPLICATION + } + } + }); + } + } + private registerActions(): void { this.registerManageProfilesSubMenu(); @@ -51,26 +77,25 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private registerManageProfilesSubMenu(): void { const that = this; - const when = ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { get title() { return localize('manageProfiles', "{0} ({1})", PROFILES_TTILE.value, that.userDataProfileService.currentProfile.name); }, submenu: ManageProfilesSubMenu, group: '5_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, order: 3 }); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { title: PROFILES_TTILE, submenu: ManageProfilesSubMenu, group: '5_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, order: 3 }); MenuRegistry.appendMenuItem(MenuId.AccountsContext, { get title() { return localize('manageProfiles', "{0} ({1})", PROFILES_TTILE.value, that.userDataProfileService.currentProfile.name); }, submenu: ManageProfilesSubMenu, group: '1_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, }); } @@ -86,7 +111,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const that = this; return registerAction2(class ProfileEntryAction extends Action2 { constructor() { - const when = ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')); super({ id: `workbench.profiles.actions.profileEntry.${profile.id}`, title: profile.name, @@ -96,7 +120,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements { id: ManageProfilesSubMenu, group: '0_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, } ] }); @@ -109,17 +133,20 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private profileStatusAccessor: IStatusbarEntryAccessor | undefined; private updateStatus(): void { - if (this.userDataProfilesService.profiles.length > 1 && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY) { - const statusBarEntry = { - name: this.userDataProfileService.currentProfile.name!, + if (this.userDataProfilesService.profiles.length > 1) { + const statusBarEntry: IStatusbarEntry = { + name: PROFILES_CATEGORY, command: 'workbench.profiles.actions.switchProfile', ariaLabel: localize('currentProfile', "Current Settings Profile is {0}", this.userDataProfileService.currentProfile.name), - text: `${PROFILES_CATEGORY}: ${this.userDataProfileService.currentProfile.name!}`, + text: `$(${Codicon.multipleWindows.id}) ${this.userDataProfileService.currentProfile.name!}`, + tooltip: localize('profileTooltip', "{0}: {1}", PROFILES_CATEGORY, this.userDataProfileService.currentProfile.name), + color: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_FOREGROUND), + backgroundColor: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_BACKGROUND) }; if (this.profileStatusAccessor) { this.profileStatusAccessor.update(statusBarEntry); } else { - this.profileStatusAccessor = this.statusBarService.addEntry(statusBarEntry, 'status.userDataProfile', StatusbarAlignment.LEFT, 1); + this.profileStatusAccessor = this.statusBarService.addEntry(statusBarEntry, 'status.userDataProfile', StatusbarAlignment.LEFT, Number.MAX_VALUE - 1); } } else { if (this.profileStatusAccessor) { @@ -129,3 +156,17 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } } } + +const STATUS_BAR_SETTINGS_PROFILE_FOREGROUND = registerColor('statusBarItem.settingsProfilesForeground', { + dark: null, + light: null, + hcDark: null, + hcLight: null +}, localize('statusBarItemSettingsProfileForeground', "Foreground color for the settings profile entry on the status bar.")); + +const STATUS_BAR_SETTINGS_PROFILE_BACKGROUND = registerColor('statusBarItem.settingsProfilesBackground', { + dark: null, + light: null, + hcDark: null, + hcLight: null +}, localize('statusBarItemSettingsProfileBackground', "Background color for the settings profile entry on the status bar.")); diff --git a/src/vs/workbench/contrib/userDataProfile/common/profileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/profileActions.ts deleted file mode 100644 index 698833c1aa8..00000000000 --- a/src/vs/workbench/contrib/userDataProfile/common/profileActions.ts +++ /dev/null @@ -1,136 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; -import { localize } from 'vs/nls'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; -import { IUserDataProfileTemplate, isUserDataProfileTemplate, IUserDataProfileWorkbenchService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - -registerAction2(class ExportProfileAction extends Action2 { - constructor() { - super({ - id: 'workbench.profiles.actions.exportProfile', - title: { - value: localize('export profile', "Export Settings as a Profile..."), - original: 'Export Settings as a Profile...' - }, - category: PROFILES_CATEGORY, - f1: true - }); - } - - async run(accessor: ServicesAccessor) { - const textFileService = accessor.get(ITextFileService); - const fileDialogService = accessor.get(IFileDialogService); - const profileService = accessor.get(IUserDataProfileWorkbenchService); - const notificationService = accessor.get(INotificationService); - - const profileLocation = await fileDialogService.showSaveDialog({ - title: localize('export profile dialog', "Save Profile"), - filters: PROFILE_FILTER, - defaultUri: joinPath(await fileDialogService.defaultFilePath(), `profile.${PROFILE_EXTENSION}`), - }); - - if (!profileLocation) { - return; - } - - const profile = await profileService.createProfile({ skipComments: true }); - await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]); - - notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY)); - } -}); - -registerAction2(class ImportProfileAction extends Action2 { - constructor() { - super({ - id: 'workbench.profiles.actions.importProfile', - title: { - value: localize('import profile', "Import Settings from a Profile..."), - original: 'Import Settings from a Profile...' - }, - category: PROFILES_CATEGORY, - f1: true - }); - } - - async run(accessor: ServicesAccessor) { - const fileDialogService = accessor.get(IFileDialogService); - const quickInputService = accessor.get(IQuickInputService); - const fileService = accessor.get(IFileService); - const requestService = accessor.get(IRequestService); - const profileService = accessor.get(IUserDataProfileWorkbenchService); - const dialogService = accessor.get(IDialogService); - - if (!(await dialogService.confirm({ - title: localize('import profile title', "Import Settings from a Profile"), - message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"), - })).confirmed) { - return; - } - - const disposables = new DisposableStore(); - const quickPick = disposables.add(quickInputService.createQuickPick()); - const updateQuickPickItems = (value?: string) => { - const selectFromFileItem: IQuickPickItem = { label: localize('select from file', "Import from profile file") }; - quickPick.items = value ? [{ label: localize('select from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; - }; - quickPick.title = localize('import profile quick pick title', "Import Settings from a Profile"); - quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import"); - quickPick.ignoreFocusOut = true; - disposables.add(quickPick.onDidChangeValue(updateQuickPickItems)); - updateQuickPickItems(); - quickPick.matchOnLabel = false; - quickPick.matchOnDescription = false; - disposables.add(quickPick.onDidAccept(async () => { - quickPick.hide(); - const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); - if (profile) { - await profileService.setProfile(profile); - } - })); - disposables.add(quickPick.onDidHide(() => disposables.dispose())); - quickPick.show(); - } - - private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise { - const profileLocation = await fileDialogService.showOpenDialog({ - canSelectFolders: false, - canSelectFiles: true, - canSelectMany: false, - filters: PROFILE_FILTER, - title: localize('import profile dialog', "Import Profile"), - }); - if (!profileLocation) { - return null; - } - const content = (await fileService.readFile(profileLocation[0])).value.toString(); - const parsed = JSON.parse(content); - return isUserDataProfileTemplate(parsed) ? parsed : null; - } - - private async getProfileFromURL(url: string, requestService: IRequestService): Promise { - const options = { type: 'GET', url }; - const context = await requestService.request(options, CancellationToken.None); - if (context.res.statusCode === 200) { - const result = await asJson(context); - return isUserDataProfileTemplate(result) ? result : null; - } else { - const message = await asText(context); - throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`); - } - } - -}); diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index 4f9f2ca2328..01a3ef3d346 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -7,38 +7,36 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { asJson, asText, IRequestService } from 'vs/platform/request/common/request'; -import { IUserDataProfileTemplate, isUserDataProfileTemplate, IUserDataProfileManagementService, IUserDataProfileWorkbenchService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER, ManageProfilesSubMenu, IUserDataProfileService, PROFILES_ENABLEMENT_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileTemplate, isUserDataProfileTemplate, IUserDataProfileManagementService, IUserDataProfileImportExportService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER, ManageProfilesSubMenu, IUserDataProfileService, PROFILES_ENABLEMENT_CONTEXT } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { - const when = ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')); super({ id: 'workbench.profiles.actions.createFromCurrentProfile', title: { - value: localize('save profile as', "Create from Current Profile..."), + value: localize('save profile as', "Create from Current Settings Profile..."), original: 'Create from Current Profile...' }, category: PROFILES_CATEGORY, f1: true, - precondition: when, + precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '1_create_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, order: 1 } ] @@ -50,7 +48,7 @@ registerAction2(class CreateFromCurrentProfileAction extends Action2 { const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); const name = await quickInputService.input({ placeHolder: localize('name', "Profile name"), - title: localize('save profile as', "Create from Current Profile..."), + title: localize('save profile as', "Create from Current Settings Profile..."), }); if (name) { await userDataProfileManagementService.createAndEnterProfile(name, undefined, true); @@ -60,21 +58,20 @@ registerAction2(class CreateFromCurrentProfileAction extends Action2 { registerAction2(class CreateEmptyProfileAction extends Action2 { constructor() { - const when = ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')); super({ id: 'workbench.profiles.actions.createProfile', title: { - value: localize('create profile', "Create an Empty Profile..."), + value: localize('create profile', "Create an Empty Settings Profile..."), original: 'Create an Empty Profile...' }, category: PROFILES_CATEGORY, f1: true, - precondition: when, + precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '1_create_profiles', - when, + when: PROFILES_ENABLEMENT_CONTEXT, order: 2 } ] @@ -96,21 +93,20 @@ registerAction2(class CreateEmptyProfileAction extends Action2 { registerAction2(class RemoveProfileAction extends Action2 { constructor() { - const when = ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')); super({ id: 'workbench.profiles.actions.removeProfile', title: { - value: localize('remove profile', "Remove Profile..."), + value: localize('remove profile', "Remove Settings Profile..."), original: 'Remove Profile...' }, category: PROFILES_CATEGORY, f1: true, - precondition: when, + precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '2_manage_profiles', - when + when: PROFILES_ENABLEMENT_CONTEXT } ] }); @@ -121,12 +117,17 @@ registerAction2(class RemoveProfileAction extends Action2 { const userDataProfileService = accessor.get(IUserDataProfileService); const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const notificationService = accessor.get(INotificationService); const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault); if (profiles.length) { const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") }); if (pick) { - await userDataProfileManagementService.removeProfile(pick.profile); + try { + await userDataProfileManagementService.removeProfile(pick.profile); + } catch (error) { + notificationService.error(error); + } } } } @@ -137,12 +138,12 @@ registerAction2(class SwitchProfileAction extends Action2 { super({ id: 'workbench.profiles.actions.switchProfile', title: { - value: localize('switch profile', "Switch Settings Profile"), - original: 'Switch Settings Profile' + value: localize('switch profile', "Switch Settings Profile..."), + original: 'Switch Settings Profile...' }, category: PROFILES_CATEGORY, f1: true, - precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')), + precondition: PROFILES_ENABLEMENT_CONTEXT, }); } @@ -152,7 +153,7 @@ registerAction2(class SwitchProfileAction extends Action2 { const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); - const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id); + const profiles = userDataProfilesService.profiles; if (profiles.length) { const picks: Array = profiles.map(profile => ({ label: profile.name!, @@ -172,7 +173,7 @@ registerAction2(class CleanupProfilesAction extends Action2 { super({ id: 'workbench.profiles.actions.cleanupProfiles', title: { - value: localize('cleanup profile', "Cleanup Profiles"), + value: localize('cleanup profile', "Cleanup Settings Profiles"), original: 'Cleanup Profiles' }, category: CATEGORIES.Developer, @@ -195,21 +196,29 @@ registerAction2(class CleanupProfilesAction extends Action2 { registerAction2(class ExportProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.exportProfile2', + id: 'workbench.profiles.actions.exportProfile', title: { - value: localize('export profile', "Export Settings as a Profile (2)..."), - original: 'Export Settings as a Profile as a Profile (2)...' + value: localize('export profile', "Export Settings Profile..."), + original: 'Export Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')), + menu: [ + { + id: ManageProfilesSubMenu, + group: '3_import_export_profiles', + when: PROFILES_ENABLEMENT_CONTEXT, + order: 1 + }, { + id: MenuId.CommandPalette + } + ] }); } async run(accessor: ServicesAccessor) { const textFileService = accessor.get(ITextFileService); const fileDialogService = accessor.get(IFileDialogService); - const profileService = accessor.get(IUserDataProfileWorkbenchService); + const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); const notificationService = accessor.get(INotificationService); const profileLocation = await fileDialogService.showSaveDialog({ @@ -222,7 +231,7 @@ registerAction2(class ExportProfileAction extends Action2 { return; } - const profile = await profileService.createProfile({ skipComments: true }); + const profile = await userDataProfileImportExportService.exportProfile({ skipComments: true }); await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]); notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY)); @@ -232,14 +241,22 @@ registerAction2(class ExportProfileAction extends Action2 { registerAction2(class ImportProfileAction extends Action2 { constructor() { super({ - id: 'workbench.profiles.actions.importProfile2', + id: 'workbench.profiles.actions.importProfile', title: { - value: localize('import profile', "Import Settings from a Profile (2)..."), - original: 'Import Settings from a Profile (2)...' + value: localize('import profile', "Import Settings Profile..."), + original: 'Import Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: ContextKeyExpr.and(PROFILES_ENABLEMENT_CONTEXT, WorkbenchStateContext.notEqualsTo('empty')), + menu: [ + { + id: ManageProfilesSubMenu, + group: '3_import_export_profiles', + when: PROFILES_ENABLEMENT_CONTEXT, + order: 2 + }, { + id: MenuId.CommandPalette + } + ] }); } @@ -248,7 +265,20 @@ registerAction2(class ImportProfileAction extends Action2 { const quickInputService = accessor.get(IQuickInputService); const fileService = accessor.get(IFileService); const requestService = accessor.get(IRequestService); - const userDataProfileMangementService = accessor.get(IUserDataProfileManagementService); + const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + const dialogService = accessor.get(IDialogService); + const contextKeyService = accessor.get(IContextKeyService); + + const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT); + + if (!isSettingProfilesEnabled) { + if (!(await dialogService.confirm({ + title: localize('import profile title', "Import Settings from a Profile"), + message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"), + })).confirmed) { + return; + } + } const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); @@ -267,12 +297,10 @@ registerAction2(class ImportProfileAction extends Action2 { quickPick.hide(); const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); if (profile) { - const name = await quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: localize('save profile as', "Create from Current Profile..."), - }); - if (name) { - await userDataProfileMangementService.createAndEnterProfileFromTemplate(name, profile); + if (isSettingProfilesEnabled) { + await userDataProfileImportExportService.importProfile(profile); + } else { + await userDataProfileImportExportService.setProfile(profile); } } })); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 33629a1b8f6..9f113cfabab 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -6,10 +6,10 @@ import { Action } from 'vs/base/common/actions'; import { getErrorMessage, isCancellationError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; -import { Disposable, DisposableStore, MutableDisposable, toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { isEqual, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import type { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyEqualsExpr, ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; 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'; @@ -59,10 +59,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; +import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -169,7 +166,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerViews(); textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider)); - registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution); this._register(Event.any(userDataSyncService.onDidChangeStatus, userDataSyncEnablementService.onDidChangeEnablement) (() => this.turningOnSync = !userDataSyncEnablementService.isEnabled() && userDataSyncService.status !== SyncStatus.Idle)); @@ -730,35 +726,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async handleConflicts([syncResource, conflicts]: [SyncResource, IResourcePreview[]]): Promise { - const useMergeEditor = this.configurationService.getValue('settingsSync.mergeEditor') ?? true; for (const conflict of conflicts) { - if (useMergeEditor) { - const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); - const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); - const input = this.instantiationService.createInstance( - MergeEditorInput, - conflict.baseResource, - { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, - { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, - conflict.previewResource, - ); - await this.editorService.openEditor(input); - } else { - const leftResourceName = localize({ key: 'leftResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); - const rightResourceName = localize('merges', "{0} (Merges)", basename(conflict.previewResource)); - await this.editorService.openEditor({ - original: { resource: conflict.remoteResource }, - modified: { resource: conflict.previewResource }, - label: localize('sideBySideLabels', "{0} ↔ {1}", leftResourceName, rightResourceName), - description: localize('sideBySideDescription', "Settings Sync"), - options: { - preserveFocus: false, - pinned: true, - revealIfVisible: true, - override: EditorResolution.DISABLED - }, - }); - } + const remoteResourceName = localize({ key: 'remoteResourceName', comment: ['remote as in file in cloud'] }, "{0} (Remote)", basename(conflict.remoteResource)); + const localResourceName = localize('localResourceName', "{0} (Local)", basename(conflict.remoteResource)); + const input = this.instantiationService.createInstance( + MergeEditorInput, + conflict.baseResource, + { title: localize('Yours', 'Yours'), description: localResourceName, detail: undefined, uri: conflict.localResource }, + { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, + conflict.previewResource, + ); + await this.editorService.openEditor(input); } } @@ -1313,7 +1291,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('accept merges title', "Accept Merge"), menu: [{ id: MenuId.MergeToolbar, - when: ContextKeyExpr.and(ContextKeyDefinedExpr.create('isMergeEditor'), ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), + when: ContextKeyExpr.and(ctxIsMergeEditor, ContextKeyEqualsExpr.create('baseResourceScheme', USER_DATA_SYNC_SCHEME)), }], }); } @@ -1394,131 +1372,3 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { return null; } } - -class AcceptChangesContribution extends Disposable implements IEditorContribution { - - static get(editor: ICodeEditor): AcceptChangesContribution | null { - return editor.getContribution(AcceptChangesContribution.ID); - } - - public static readonly ID = 'editor.contrib.acceptChangesButton'; - - private acceptChangesButton: FloatingClickWidget | undefined; - - constructor( - private editor: ICodeEditor, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @INotificationService private readonly notificationService: INotificationService, - @IDialogService private readonly dialogService: IDialogService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, - ) { - super(); - - this.update(); - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.editor.onDidChangeModel(() => this.update())); - this._register(this.userDataSyncService.onDidChangeConflicts(() => this.update())); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('diffEditor.renderSideBySide'))(() => this.update())); - } - - private update(): void { - if (!this.shouldShowButton(this.editor)) { - this.disposeAcceptChangesWidgetRenderer(); - return; - } - - this.createAcceptChangesWidgetRenderer(); - } - - private shouldShowButton(editor: ICodeEditor): boolean { - const model = editor.getModel(); - if (!model) { - return false; // we need a model - } - - if (!this.userDataSyncEnablementService.isEnabled()) { - return false; - } - - const syncResourceConflicts = this.getSyncResourceConflicts(model.uri); - if (!syncResourceConflicts) { - return false; - } - - if (syncResourceConflicts[1].some(({ previewResource }) => isEqual(previewResource, model.uri))) { - return true; - } - - if (syncResourceConflicts[1].some(({ remoteResource }) => isEqual(remoteResource, model.uri))) { - return this.configurationService.getValue('diffEditor.renderSideBySide'); - } - - return false; - } - - private createAcceptChangesWidgetRenderer(): void { - if (!this.acceptChangesButton) { - const resource = this.editor.getModel()!.uri; - const [syncResource, conflicts] = this.getSyncResourceConflicts(resource)!; - const isRemote = conflicts.some(({ remoteResource }) => isEqual(remoteResource, resource)); - const acceptRemoteLabel = localize('accept remote', "Accept Remote"); - const acceptMergesLabel = localize('accept merges', "Accept Merges"); - const acceptRemoteButtonLabel = localize('accept remote button', "Accept &&Remote"); - const acceptMergesButtonLabel = localize('accept merges button', "Accept &&Merges"); - this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, isRemote ? acceptRemoteLabel : acceptMergesLabel, null); - this._register(this.acceptChangesButton.onClick(async () => { - const model = this.editor.getModel(); - if (model) { - this.telemetryService.publicLog2<{ source: string; action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: syncResource, action: isRemote ? 'acceptRemote' : 'acceptLocal' }); - const syncAreaLabel = getSyncAreaLabel(syncResource); - const result = await this.dialogService.confirm({ - type: 'info', - title: isRemote - ? localize('Sync accept remote', "{0}: {1}", SYNC_TITLE, acceptRemoteLabel) - : localize('Sync accept merges', "{0}: {1}", SYNC_TITLE, acceptMergesLabel), - message: isRemote - ? localize('confirm replace and overwrite local', "Would you like to accept remote {0} and replace local {1}?", syncAreaLabel.toLowerCase(), syncAreaLabel.toLowerCase()) - : localize('confirm replace and overwrite remote', "Would you like to accept merges and replace remote {0}?", syncAreaLabel.toLowerCase()), - primaryButton: isRemote ? acceptRemoteButtonLabel : acceptMergesButtonLabel - }); - if (result.confirmed) { - try { - await this.userDataSyncService.accept(syncResource, model.uri, model.getValue(), true); - } catch (e) { - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.LocalPreconditionFailed) { - const syncResourceCoflicts = this.userDataSyncService.conflicts.filter(syncResourceCoflicts => syncResourceCoflicts[0] === syncResource)[0]; - if (syncResourceCoflicts && conflicts.some(conflict => isEqual(conflict.previewResource, model.uri) || isEqual(conflict.remoteResource, model.uri))) { - this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); - } - } else { - this.notificationService.error(localize('accept failed', "Error while accepting changes. Please check [logs]({0}) for more details.", `command:${SHOW_SYNC_LOG_COMMAND_ID}`)); - } - } - } - } - })); - - this.acceptChangesButton.render(); - } - } - - private getSyncResourceConflicts(resource: URI): [SyncResource, IResourcePreview[]] | undefined { - return this.userDataSyncService.conflicts.filter(([, conflicts]) => conflicts.some(({ previewResource, remoteResource }) => isEqual(previewResource, resource) || isEqual(remoteResource, resource)))[0]; - } - - private disposeAcceptChangesWidgetRenderer(): void { - dispose(this.acceptChangesButton); - this.acceptChangesButton = undefined; - } - - override dispose(): void { - this.disposeAcceptChangesWidgetRenderer(); - super.dispose(); - } -} diff --git a/src/vs/workbench/contrib/watermark/browser/media/watermark.css b/src/vs/workbench/contrib/watermark/browser/media/watermark.css index 29eba5b8a5f..2c45ebd4b16 100644 --- a/src/vs/workbench/contrib/watermark/browser/media/watermark.css +++ b/src/vs/workbench/contrib/watermark/browser/media/watermark.css @@ -23,8 +23,6 @@ text-align: center; white-space: nowrap; overflow: hidden; - /* Watermark should show even over opaque backgrounds */ - z-index: 1000; } .monaco-workbench .part.editor > .content.empty > .watermark > .watermark-box { diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index 74b541681a6..dcbddfc4097 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -691,8 +691,8 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'resetGettingStartedProgress', - category: 'Developer', - title: 'Reset Welcome Page Walkthrough Progress', + category: { original: 'Developer', value: localize('developer', "Developer") }, + title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") }, f1: true }); } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 15bcde982e2..9e045e9638c 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -237,7 +237,12 @@ import { ModifierKeyEmitter } from 'vs/base/browser/dom'; 'scope': ConfigurationScope.APPLICATION, 'description': localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."), 'included': isMacintosh - } + }, + 'window.experimental.useSandbox': { + type: 'boolean', + description: localize('experimentalUseSandbox', "Experimental: When enabled, the window will have sandbox mode enabled via Electron API."), + default: false + }, } }); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 86706f54945..38363be6614 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -148,12 +148,9 @@ export class DesktopMain extends Disposable { // // NOTE: Please do NOT register services here. Use `registerSingleton()` // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service + // desktop and web or `workbench.desktop.main.ts` if the service // is desktop only. // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -205,12 +202,9 @@ export class DesktopMain extends Disposable { // // NOTE: Please do NOT register services here. Use `registerSingleton()` // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service + // desktop and web or `workbench.desktop.main.ts` if the service // is desktop only. // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -243,19 +237,16 @@ export class DesktopMain extends Disposable { // User Data Profiles const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService, fileService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme)); + const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme), userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: Please do NOT register services here. Use `registerSingleton()` // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service + // desktop and web or `workbench.desktop.main.ts` if the service // is desktop only. // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -306,12 +297,9 @@ export class DesktopMain extends Disposable { // // NOTE: Please do NOT register services here. Use `registerSingleton()` // from `workbench.common.main.ts` if the service is shared between - // desktop and web or `workbench.sandbox.main.ts` if the service + // desktop and web or `workbench.desktop.main.ts` if the service // is desktop only. // - // DO NOT add services to `workbench.desktop.main.ts`, always add - // to `workbench.sandbox.main.ts` to support our Electron sandbox - // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 571cd3a5f0f..af2e33d72d2 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -68,8 +68,6 @@ import { dirname } from 'vs/base/common/resources'; export class NativeWindow extends Disposable { - private static REMEMBER_PROXY_CREDENTIALS_KEY = 'window.rememberProxyCredentials'; - private touchBarMenu: IMenu | undefined; private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; @@ -215,7 +213,8 @@ export class NativeWindow extends Disposable { // Proxy Login Dialog ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }) => { - const rememberCredentials = this.storageService.getBoolean(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.APPLICATION); + const rememberCredentialsKey = 'window.rememberProxyCredentials'; + const rememberCredentials = this.storageService.getBoolean(rememberCredentialsKey, StorageScope.APPLICATION); const result = await this.dialogService.input(Severity.Warning, localize('proxyAuthRequired', "Proxy Authentication Required"), [ localize({ key: 'loginButton', comment: ['&& denotes a mnemonic'] }, "&&Log In"), @@ -245,9 +244,9 @@ export class NativeWindow extends Disposable { // Update state based on checkbox if (result.checkboxChecked) { - this.storageService.store(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE); + this.storageService.store(rememberCredentialsKey, true, StorageScope.APPLICATION, StorageTarget.MACHINE); } else { - this.storageService.remove(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.APPLICATION); + this.storageService.remove(rememberCredentialsKey, StorageScope.APPLICATION); } // Reply back to main side with credentials @@ -286,7 +285,7 @@ export class NativeWindow extends Disposable { const file = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file }); // Represented Filename - this.updateRepresentedFilename(file?.fsPath); + this.nativeHostService.setRepresentedFilename(file?.fsPath ?? ''); // Custom title menu this.provideCustomTitleContextMenu(file?.fsPath); @@ -573,10 +572,6 @@ export class NativeWindow extends Disposable { } } - private updateRepresentedFilename(filePath: string | undefined): void { - this.nativeHostService.setRepresentedFilename(filePath ? filePath : ''); - } - private provideCustomTitleContextMenu(filePath: string | undefined): void { // Clear old menu diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index 6bb3eca341a..3d0d3b0206d 100644 --- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -7,7 +7,6 @@ import { localize } from 'vs/nls'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { forEach } from 'vs/base/common/collections'; import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, IMenuItem, ISubmenuItem } from 'vs/platform/actions/common/actions'; @@ -61,6 +60,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.EditorContextCopy, description: localize('menus.editorContextCopyAs', "'Copy as' submenu in the editor context menu") }, + { + key: 'editor/context/share', + id: MenuId.EditorContextShare, + description: localize('menus.editorContextShare', "'Share' submenu in the editor context menu"), + proposed: 'contribShareMenu' + }, { key: 'explorer/context', id: MenuId.ExplorerContext, @@ -249,6 +254,12 @@ const apiMenus: IAPIMenu[] = [ description: localize('file.newFile', "The 'New File...' quick pick, shown on welcome page and File menu."), supportsSubmenus: false, }, + { + key: 'file/share', + id: MenuId.MenubarShare, + description: localize('menus.share', "Share submenu shown in the top level File menu."), + proposed: 'contribShareMenu' + }, { key: 'editor/inlineCompletions/actions', id: MenuId.InlineCompletionsActions, @@ -674,44 +685,45 @@ submenusExtensionPoint.setHandler(extensions => { for (const extension of extensions) { const { value, collector } = extension; - forEach(value, entry => { - if (!schema.isValidSubmenu(entry.value, collector)) { + for (const [, submenuInfo] of Object.entries(value)) { + + if (!schema.isValidSubmenu(submenuInfo, collector)) { return; } - if (!entry.value.id) { - collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", entry.value.id)); + if (!submenuInfo.id) { + collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", submenuInfo.id)); return; } - if (_submenus.has(entry.value.id)) { - collector.info(localize('submenuId.duplicate.id', "The `{0}` submenu was already previously registered.", entry.value.id)); + if (_submenus.has(submenuInfo.id)) { + collector.info(localize('submenuId.duplicate.id', "The `{0}` submenu was already previously registered.", submenuInfo.id)); return; } - if (!entry.value.label) { - collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", entry.value.label)); + if (!submenuInfo.label) { + collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", submenuInfo.label)); return; } let absoluteIcon: { dark: URI; light?: URI } | ThemeIcon | undefined; - if (entry.value.icon) { - if (typeof entry.value.icon === 'string') { - absoluteIcon = ThemeIcon.fromString(entry.value.icon) || { dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon) }; + if (submenuInfo.icon) { + if (typeof submenuInfo.icon === 'string') { + absoluteIcon = ThemeIcon.fromString(submenuInfo.icon) || { dark: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon) }; } else { absoluteIcon = { - dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon.dark), - light: resources.joinPath(extension.description.extensionLocation, entry.value.icon.light) + dark: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon.dark), + light: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon.light) }; } } const item: IRegisteredSubmenu = { - id: new MenuId(`api:${entry.value.id}`), - label: entry.value.label, + id: new MenuId(`api:${submenuInfo.id}`), + label: submenuInfo.label, icon: absoluteIcon }; - _submenus.set(entry.value.id, item); - }); + _submenus.set(submenuInfo.id, item); + } } }); @@ -736,19 +748,19 @@ menusExtensionPoint.setHandler(extensions => { for (const extension of extensions) { const { value, collector } = extension; - forEach(value, entry => { - if (!schema.isValidItems(entry.value, collector)) { - return; + for (const entry of Object.entries(value)) { + if (!schema.isValidItems(entry[1], collector)) { + continue; } - let menu = _apiMenusByKey.get(entry.key); + let menu = _apiMenusByKey.get(entry[0]); if (!menu) { - const submenu = _submenus.get(entry.key); + const submenu = _submenus.get(entry[0]); if (submenu) { menu = { - key: entry.key, + key: entry[0], id: submenu.id, description: '' }; @@ -756,16 +768,16 @@ menusExtensionPoint.setHandler(extensions => { } if (!menu) { - collector.info(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key)); - return; + collector.info(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry[0])); + continue; } if (menu.proposed && !isProposedApiEnabled(extension.description, menu.proposed)) { - collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier. It requires 'package.json#enabledApiProposals: [\"{1}\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {2}", entry.key, menu.proposed, extension.description.identifier.value)); - return; + collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier. It requires 'package.json#enabledApiProposals: [\"{1}\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {2}", entry[0], menu.proposed, extension.description.identifier.value)); + continue; } - for (const menuItem of entry.value) { + for (const menuItem of entry[1]) { let item: IMenuItem | ISubmenuItem; if (schema.isMenuItem(menuItem)) { @@ -805,7 +817,7 @@ menusExtensionPoint.setHandler(extensions => { } if (submenuRegistrations.has(submenu.id.id)) { - collector.warn(localize('submenuItem.duplicate', "The `{0}` submenu was already contributed to the `{1}` menu.", menuItem.submenu, entry.key)); + collector.warn(localize('submenuItem.duplicate', "The `{0}` submenu was already contributed to the `{1}` menu.", menuItem.submenu, entry[0])); continue; } @@ -827,7 +839,7 @@ menusExtensionPoint.setHandler(extensions => { item.when = ContextKeyExpr.deserialize(menuItem.when); items.push({ id: menu.id, item }); } - }); + } } _menuRegistrations.add(MenuRegistry.appendMenuItems(items)); diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 84635d4dade..6830c4c82fa 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -47,7 +47,7 @@ const FIRST_PARTY_ALLOWED_EXTENSIONS = [ export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] { const accountKey = `${providerId}-${accountName}-usages`; - const storedUsages = storageService.get(accountKey, StorageScope.PROFILE); + const storedUsages = storageService.get(accountKey, StorageScope.APPLICATION); let usages: IAccountUsage[] = []; if (storedUsages) { try { @@ -62,7 +62,7 @@ export function readAccountUsages(storageService: IStorageService, providerId: s export function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void { const accountKey = `${providerId}-${accountName}-usages`; - storageService.remove(accountKey, StorageScope.PROFILE); + storageService.remove(accountKey, StorageScope.APPLICATION); } export function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) { @@ -84,7 +84,7 @@ export function addAccountUsage(storageService: IStorageService, providerId: str }); } - storageService.store(accountKey, JSON.stringify(usages), StorageScope.PROFILE, StorageTarget.MACHINE); + storageService.store(accountKey, JSON.stringify(usages), StorageScope.APPLICATION, StorageTarget.MACHINE); } export type AuthenticationSessionInfo = { readonly id: string; readonly accessToken: string; readonly providerId: string; readonly canSignOut?: boolean }; @@ -112,7 +112,7 @@ export interface AllowedExtension { export function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { let trustedExtensions: AllowedExtension[] = []; try { - const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.PROFILE); + const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION); if (trustedExtensionSrc) { trustedExtensions = JSON.parse(trustedExtensionSrc); } @@ -410,7 +410,7 @@ export class AuthenticationService extends Disposable implements IAuthentication allowList[index].allowed = isAllowed; } - await this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.PROFILE, StorageTarget.USER); + await this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.APPLICATION, StorageTarget.USER); } async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { @@ -475,7 +475,7 @@ export class AuthenticationService extends Disposable implements IAuthentication this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); this.removeAccessRequest(providerId, extensionId); - this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.APPLICATION, StorageTarget.MACHINE); quickPick.dispose(); resolve(session); @@ -615,7 +615,7 @@ export class AuthenticationService extends Disposable implements IAuthentication this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); // And also set it as the preferred account for the extension - storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.PROFILE, StorageTarget.MACHINE); + storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.APPLICATION, StorageTarget.MACHINE); } }); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 72a1946338d..bba9911a158 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -26,7 +26,7 @@ import { JSONEditingService } from 'vs/workbench/services/configuration/common/j import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { mark } from 'vs/base/common/performance'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -35,7 +35,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { delta, distinct } from 'vs/base/common/arrays'; -import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { isUndefined } from 'vs/base/common/types'; @@ -43,6 +43,8 @@ import { localize } from 'vs/nls'; import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; +import { VSBuffer } from 'vs/base/common/buffer'; function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined { return userDataProfile.isDefault @@ -711,20 +713,44 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat } private onUserDataProfileChanged(e: DidChangeUserDataProfileEvent): void { - const promises: Promise[] = []; - promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration))); - if (e.previous.isDefault !== e.profile.isDefault) { - this.createApplicationConfiguration(); - if (this.applicationConfiguration) { - promises.push(this.reloadApplicationConfiguration(true)); - } - } e.join((async () => { + if (e.preserveData) { + await Promise.all([ + this.copyProfileSettings(e.previous.settingsResource, e.profile.settingsResource), + this.fileService.copy(e.previous.tasksResource, e.profile.tasksResource) + ]); + } + const promises: Promise[] = []; + promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration))); + if (e.previous.isDefault !== e.profile.isDefault) { + this.createApplicationConfiguration(); + if (this.applicationConfiguration) { + promises.push(this.reloadApplicationConfiguration(true)); + } + } const [localUser, application] = await Promise.all(promises); await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration); })()); } + private async copyProfileSettings(from: URI, to: URI): Promise { + let fromContent: string | undefined; + try { + fromContent = (await this.fileService.readFile(from)).value.toString(); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + throw error; + } + } + if (!fromContent) { + return; + } + const allSettings = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const applicationSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.APPLICATION); + const toContent = updateIgnoredSettings(fromContent, '{}', applicationSettings, {}); + await this.fileService.writeFile(to, VSBuffer.fromString(toContent)); + } + private onDefaultConfigurationChanged(configurationModel: ConfigurationModel, properties?: string[]): void { if (this.workspace) { const previousData = this._configuration.toData(); @@ -1212,7 +1238,7 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo } const result: IStringDictionary = {}; - forEach(properties, ({ key, value }) => { + Object.entries(properties).forEach(([key, value]) => { if (!value.restricted) { result[key] = value; } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 8b7fb8df39f..6412bd0e4c3 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -111,7 +111,7 @@ suite('ConfigurationEditingService', () => { environmentService.policyFile = joinPath(workspaceFolder, 'policies.json'); instantiationService.stub(IEnvironmentService, environmentService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); + userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); @@ -192,7 +192,7 @@ suite('ConfigurationEditingService', () => { test('do not notify error', async () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, notify: null!, error: null!, info: null!, warn: null!, status: null!, setFilter: null! }); + instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: undefined, doNotDisturbMode: false, onDidAddNotification: undefined!, onDidRemoveNotification: undefined!, onDidChangeDoNotDisturbMode: undefined!, notify: null!, error: null!, info: null!, warn: null!, status: null! }); try { await testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }); } catch (error) { diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 3a1d3a6ca2b..41573109ed5 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -50,6 +50,7 @@ import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -86,7 +87,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -127,7 +128,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -148,7 +149,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -196,7 +197,7 @@ suite('WorkspaceContextService - Workspace', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -255,7 +256,7 @@ suite('WorkspaceContextService - Workspace Editing', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -499,7 +500,7 @@ suite('WorkspaceService - Initialization', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -759,7 +760,7 @@ suite('WorkspaceConfigurationService - Folder', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1096,7 +1097,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('update workspace configuration', () => { return testObject.updateValue('tasks.service.testSetting', 'value', ConfigurationTarget.WORKSPACE) - .then(() => assert.strictEqual(testObject.getValue('tasks.service.testSetting'), 'value')); + .then(() => assert.strictEqual(testObject.getValue(TasksSchemaProperties.ServiceTestSetting), 'value')); }); test('update resource configuration', () => { @@ -1150,7 +1151,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('update tasks configuration', () => { return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, ConfigurationTarget.WORKSPACE) - .then(() => assert.deepStrictEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); + .then(() => assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] })); }); test('update user configuration should trigger change event before promise is resolve', () => { @@ -1425,7 +1426,7 @@ suite('WorkspaceConfigurationService - Profiles', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService)); workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1613,7 +1614,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); @@ -1994,7 +1995,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { }; await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['tasks'], value: expectedTasksConfiguration }], true); await testObject.reloadConfiguration(); - const actual = testObject.getValue('tasks'); + const actual = testObject.getValue(TasksSchemaProperties.Tasks); assert.deepStrictEqual(actual, expectedTasksConfiguration); }); @@ -2119,7 +2120,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { test('update tasks configuration in a folder', async () => { const workspace = workspaceContextService.getWorkspace(); await testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER); - assert.deepStrictEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }); + assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks, { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }); }); test('update launch configuration in a workspace', async () => { @@ -2132,7 +2133,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const workspace = workspaceContextService.getWorkspace(); const tasks = { 'version': '2.0.0', tasks: [{ 'label': 'myTask' }] }; await testObject.updateValue('tasks', tasks, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true); - assert.deepStrictEqual(testObject.getValue('tasks'), tasks); + assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), tasks); }); test('configuration of newly added folder is available on configuration change event', async () => { @@ -2276,7 +2277,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); diff --git a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts index 059d8e46456..07053d26b4d 100644 --- a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as Types from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor'; -import { IStringDictionary, forEach } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { IConfigurationService, IConfigurationOverrides, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -157,9 +157,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR if (!newMapping) { return false; } - forEach(newMapping, (entry) => { - fullMapping.set(entry.key, entry.value); - }); + for (const [key, value] of Object.entries(newMapping)) { + fullMapping.set(key, value); + } return true; } @@ -256,20 +256,21 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR } } } - this._contributedVariables.forEach((value, contributed: string) => { + for (const contributed of this._contributedVariables.keys()) { if ((variables.indexOf(contributed) < 0) && (object.indexOf('${' + contributed + '}') >= 0)) { variables.push(contributed); } - }); + } } else if (Types.isArray(object)) { - object.forEach(value => { + for (const value of object) { this.findVariables(value, variables); - }); + + } } else if (object) { - Object.keys(object).forEach(key => { - const value = object[key]; + for (const value of Object.values(object)) { this.findVariables(value, variables); - }); + + } } } @@ -315,11 +316,11 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR missingAttribute('description'); } if (Types.isArray(info.options)) { - info.options.forEach(pickOption => { + for (const pickOption of info.options) { if (!Types.isString(pickOption) && !Types.isString(pickOption.value)) { missingAttribute('value'); } - }); + } } else { missingAttribute('options'); } @@ -327,7 +328,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR value: string; } const picks = new Array(); - info.options.forEach(pickOption => { + for (const pickOption of info.options) { const value = Types.isString(pickOption) ? pickOption : pickOption.value; const label = Types.isString(pickOption) ? undefined : pickOption.label; @@ -343,7 +344,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR } else { picks.push(item); } - }); + } const pickOptions: IPickOptions = { placeHolder: info.description, matchOnDetail: true, ignoreFocusLost: true }; return this.quickInputService.pick(picks, pickOptions, undefined).then(resolvedInput => { if (resolvedInput) { diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts index 2d99f4bb24b..21ea8e0dcac 100644 --- a/src/vs/workbench/services/editor/browser/editorResolverService.ts +++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts @@ -517,7 +517,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver } /** - * Given a resource and an editorId, returns all editors open for that resouce and editorId. + * Given a resource and an editorId, returns all editors open for that resource and editorId. * @param resource The resource specified * @param editorId The editorID * @returns A list of editors @@ -671,7 +671,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver // Create the editor picker const editorPicker = this.quickInputService.createQuickPick(); const placeHolderMessage = showDefaultPicker ? - localize('prompOpenWith.updateDefaultPlaceHolder', "Select new default editor for '{0}'", `*${extname(resource)}`) : + localize('promptOpenWith.updateDefaultPlaceHolder', "Select new default editor for '{0}'", `*${extname(resource)}`) : localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)); editorPicker.placeholder = placeHolderMessage; editorPicker.canAcceptInBackground = true; @@ -746,7 +746,7 @@ export class EditorResolverService extends Disposable implements IEditorResolver private sendEditorResolutionTelemetry(chosenInput: EditorInput): void { type editorResolutionClassification = { - viewType: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The id of the editor opened. Used to gain an undertsanding of what editors are most popular' }; + viewType: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The id of the editor opened. Used to gain an understanding of what editors are most popular' }; owner: 'lramos15'; comment: 'An event that fires when an editor type is picked'; }; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 631b3db439e..82a2541576a 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -83,6 +83,9 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi @memoize get userDataSyncLogResource(): URI { return joinPath(this.logsHome, 'userDataSync.log'); } + @memoize + get editSessionsLogResource(): URI { return joinPath(this.logsHome, 'editSessions.log'); } + @memoize get sync(): 'on' | 'off' | undefined { return undefined; } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index 264a59acbc7..7c9d42c8621 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -147,7 +147,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench throw new Error(localize('cannot change enablement environment', "Cannot change enablement of {0} extension because it is enabled in environment", extension.manifest.displayName || extension.identifier.id)); } - switch (this.getEnablementState(extension)) { + this.throwErrorIfEnablementStateCannotBeChanged(extension, this.getEnablementState(extension), donotCheckDependencies); + } + + private throwErrorIfEnablementStateCannotBeChanged(extension: IExtension, enablementStateOfExtension: EnablementState, donotCheckDependencies?: boolean): void { + switch (enablementStateOfExtension) { case EnablementState.DisabledByEnvironment: throw new Error(localize('cannot change disablement environment', "Cannot change enablement of {0} extension because it is disabled in environment", extension.manifest.displayName || extension.identifier.id)); case EnablementState.DisabledByVirtualWorkspace: @@ -219,30 +223,60 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } private getExtensionsToEnableRecursively(extensions: IExtension[], allExtensions: ReadonlyArray, enablementState: EnablementState, options: { dependencies: boolean; pack: boolean }, checked: IExtension[] = []): IExtension[] { - const toCheck = extensions.filter(e => checked.indexOf(e) === -1); - if (toCheck.length) { - for (const extension of toCheck) { - checked.push(extension); - } - const extensionsToDisable = allExtensions.filter(i => { - if (checked.indexOf(i) !== -1) { - return false; - } - if (this.getEnablementState(i) === enablementState) { - return false; - } - return (options.dependencies || options.pack) - && extensions.some(extension => - (options.dependencies && extension.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, i.identifier))) - || (options.pack && extension.manifest.extensionPack?.some(id => areSameExtensions({ id }, i.identifier))) - ); - }); - if (extensionsToDisable.length) { - extensionsToDisable.push(...this.getExtensionsToEnableRecursively(extensionsToDisable, allExtensions, enablementState, options, checked)); - } - return extensionsToDisable; + if (!options.dependencies && !options.pack) { + return []; } - return []; + + const toCheck = extensions.filter(e => checked.indexOf(e) === -1); + if (!toCheck.length) { + return []; + } + + for (const extension of toCheck) { + checked.push(extension); + } + + const extensionsToDisable: IExtension[] = []; + for (const extension of allExtensions) { + // Extension is already checked + if (checked.some(e => areSameExtensions(e.identifier, extension.identifier))) { + continue; + } + + const enablementStateOfExtension = this.getEnablementState(extension); + // Extension enablement state is same as the end enablement state + if (enablementStateOfExtension === enablementState) { + continue; + } + + // Check if the extension is a dependency or in extension pack + if (extensions.some(e => + (options.dependencies && e.manifest.extensionDependencies?.some(id => areSameExtensions({ id }, extension.identifier))) + || (options.pack && e.manifest.extensionPack?.some(id => areSameExtensions({ id }, extension.identifier))))) { + + const index = extensionsToDisable.findIndex(e => areSameExtensions(e.identifier, extension.identifier)); + + // Extension is not aded to the disablement list so add it + if (index === -1) { + extensionsToDisable.push(extension); + } + + // Extension is there already in the disablement list. + else { + try { + // Replace only if the enablement state can be changed + this.throwErrorIfEnablementStateCannotBeChanged(extension, enablementStateOfExtension, true); + extensionsToDisable.splice(index, 1, extension); + } catch (error) { /*Do not add*/ } + } + } + } + + if (extensionsToDisable.length) { + extensionsToDisable.push(...this.getExtensionsToEnableRecursively(extensionsToDisable, allExtensions, enablementState, options, checked)); + } + + return extensionsToDisable; } private _setUserEnablementState(extension: IExtension, newState: EnablementState): Promise { @@ -580,9 +614,9 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } private _onDidChangeExtensions(added: ReadonlyArray, removed: ReadonlyArray): void { - const disabledByTrustExtensions = added.filter(e => this.getEnablementState(e) === EnablementState.DisabledByTrustRequirement); - if (disabledByTrustExtensions.length) { - this._onEnablementChanged.fire(disabledByTrustExtensions); + const disabledExtensions = added.filter(e => !this.isEnabledEnablementState(this.getEnablementState(e))); + if (disabledExtensions.length) { + this._onEnablementChanged.fire(disabledExtensions); } removed.forEach(({ identifier }) => this._reset(identifier)); } diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index faf1645bb6d..fb5c4babf4a 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -39,7 +39,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IProductService } from 'vs/platform/product/common/productService'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; import Severity from 'vs/base/common/severity'; -import { IStringDictionary, forEach } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string }; type ExtensionInfo = { readonly id: string; preRelease: boolean }; @@ -737,7 +737,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten let packageNLSUris: Map | undefined; if (e.packageNLSUris) { packageNLSUris = new Map(); - forEach(e.packageNLSUris, (entry) => packageNLSUris!.set(entry.key, URI.revive(entry.value))); + Object.entries(e.packageNLSUris).forEach(([key, value]) => packageNLSUris!.set(key, URI.revive(value))); } webExtensions.push({ diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 5e3c46b1a5b..9c9683c1c3b 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -13,8 +13,7 @@ import { FileAccess } from 'vs/base/common/network'; export type DidChangeProfileExtensionsEvent = { readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }; export interface IProfileAwareExtensionManagementService extends IExtensionManagementService { - onDidChangeProfileExtensions: Event; - switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise; + readonly onDidChangeProfileExtensions: Event; } export interface IExtensionManagementServer { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index 233e0bc56c2..b16029ca565 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -7,6 +7,7 @@ import { localize } from 'vs/nls'; import { ExtensionInstallLocation, IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Schemas } from 'vs/base/common/network'; +import { Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -14,7 +15,7 @@ import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService'; import { IExtension } from 'vs/platform/extensions/common/extensions'; -import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService'; +import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; export class ExtensionManagementServerService implements IExtensionManagementServerService { @@ -31,7 +32,9 @@ export class ExtensionManagementServerService implements IExtensionManagementSer ) { const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = instantiationService.createInstance(NativeProfileAwareExtensionManagementService, remoteAgentConnection.getChannel('extensions'), undefined); + const extensionManagementService = new class extends ExtensionManagementChannelClient { + readonly onDidChangeProfileExtensions = Event.None; + }(remoteAgentConnection.getChannel('extensions')); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 6c1cba6e975..2c371645082 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IProfileAwareExtensionManagementService, IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; +import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -20,7 +20,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -export class WebExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService, IProfileAwareExtensionManagementService { +export class WebExtensionManagementService extends AbstractExtensionManagementService implements IProfileAwareExtensionManagementService { declare readonly _serviceBrand: undefined; @@ -100,13 +100,11 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return local; } - async switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise { } - protected createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions): IInstallExtensionTask { return new InstallExtensionTask(manifest, extension, options, this.webExtensionsScannerService); } - protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask { + protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask { return new UninstallExtensionTask(extension, options, this.webExtensionsScannerService); } @@ -191,7 +189,7 @@ class UninstallExtensionTask extends AbstractExtensionTask implements IUni constructor( readonly extension: ILocalExtension, - options: UninstallExtensionTaskOptions, + options: UninstallOptions, private readonly webExtensionsScannerService: IWebExtensionsScannerService, ) { super(); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts index ece4c6091b0..b6831100888 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts @@ -15,8 +15,9 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService'; +import { NativeExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export class ExtensionManagementServerService extends Disposable implements IExtensionManagementServerService { @@ -30,13 +31,13 @@ export class ExtensionManagementServerService extends Disposable implements IExt @ISharedProcessService sharedProcessService: ISharedProcessService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILabelService labelService: ILabelService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IInstantiationService instantiationService: IInstantiationService, ) { super(); - const localExtensionManagementService = this._register(instantiationService.createInstance(NativeProfileAwareExtensionManagementService, sharedProcessService.getChannel('extensions'), userDataProfileService.currentProfile.extensionsResource)); + const localExtensionManagementService = this._register(instantiationService.createInstance(NativeExtensionManagementService, sharedProcessService.getChannel('extensions'))); this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") }; - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(localExtensionManagementService.switchExtensionsProfile(e.profile.extensionsResource)))); const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { const extensionManagementService = instantiationService.createInstance(NativeRemoteExtensionManagementService, remoteAgentConnection.getChannel('extensions'), this.localExtensionManagementServer); diff --git a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts similarity index 54% rename from src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts rename to src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index 291e62a8317..cdd2bdad7db 100644 --- a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -7,15 +7,19 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { URI } from 'vs/base/common/uri'; -import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { Emitter, Event } from 'vs/base/common/event'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { delta } from 'vs/base/common/arrays'; import { compare } from 'vs/base/common/strings'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { EXTENSIONS_RESOURCE_NAME } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { joinPath } from 'vs/base/common/resources'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -export class NativeProfileAwareExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { +export class NativeExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { private readonly disposables = this._register(new DisposableStore()); @@ -31,42 +35,50 @@ export class NativeProfileAwareExtensionManagementService extends ExtensionManag private readonly _onDidChangeProfileExtensions = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); readonly onDidChangeProfileExtensions = this._onDidChangeProfileExtensions.event; - constructor(channel: IChannel, private extensionsProfileResource: URI | undefined, + constructor( + channel: IChannel, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { super(channel); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); } private filterEvent({ profileLocation, applicationScoped }: { profileLocation?: URI; applicationScoped?: boolean }): boolean { - return applicationScoped || this.uriIdentityService.extUri.isEqual(this.extensionsProfileResource, profileLocation); + return applicationScoped || this.uriIdentityService.extUri.isEqual(this.userDataProfileService.currentProfile.extensionsResource, profileLocation); } override install(vsix: URI, options?: InstallVSIXOptions): Promise { - return super.install(vsix, { ...options, profileLocation: this.extensionsProfileResource }); + return super.install(vsix, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); } override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - return super.installFromGallery(extension, { ...installOptions, profileLocation: this.extensionsProfileResource }); + return super.installFromGallery(extension, { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); } override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - return super.uninstall(extension, { ...options, profileLocation: this.extensionsProfileResource }); + return super.uninstall(extension, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }); } override getInstalled(type: ExtensionType | null = null): Promise { - return super.getInstalled(type, this.extensionsProfileResource); + return super.getInstalled(type, this.userDataProfileService.currentProfile.extensionsResource); } - async switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise { - if (this.uriIdentityService.extUri.isEqual(extensionsProfileResource, this.extensionsProfileResource)) { - return; - } - const oldExtensions = await this.getInstalled(ExtensionType.User); - this.extensionsProfileResource = extensionsProfileResource; - const newExtensions = await this.getInstalled(ExtensionType.User); - const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(ExtensionIdentifier.toKey(a.identifier.id), ExtensionIdentifier.toKey(b.identifier.id))); - if (added.length || removed.length) { - this._onDidChangeProfileExtensions.fire({ added, removed }); + private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + const previousExtensionsResource = e.previous.extensionsResource ?? joinPath(e.previous.location, EXTENSIONS_RESOURCE_NAME); + const oldExtensions = await super.getInstalled(ExtensionType.User, previousExtensionsResource); + if (e.preserveData) { + const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(oldExtensions + .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ + .map(async e => ([e, await this.getMetadata(e)]))); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, e.profile.extensionsResource!); + } else { + const newExtensions = await this.getInstalled(ExtensionType.User); + const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); + if (added.length || removed.length) { + this._onDidChangeProfileExtensions.fire({ added, removed }); + } } } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index 53c178a68f6..b9202112131 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { Event } from 'vs/base/common/event'; +import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -17,14 +18,15 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { generateUuid } from 'vs/base/common/uuid'; import { joinPath } from 'vs/base/common/resources'; -import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Promises } from 'vs/base/common/async'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -export class NativeRemoteExtensionManagementService extends NativeProfileAwareExtensionManagementService implements IExtensionManagementService { +export class NativeRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { + + readonly onDidChangeProfileExtensions = Event.None; constructor( channel: IChannel, @@ -35,9 +37,8 @@ export class NativeRemoteExtensionManagementService extends NativeProfileAwareEx @IProductService private readonly productService: IProductService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, - @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(channel, undefined, uriIdentityService); + super(channel); } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 3fc8b045751..1bfaa725a8a 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -5,10 +5,10 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { IExtensionManagementService, DidUninstallExtensionEvent, ILocalExtension, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, ExtensionInstallLocation, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, ExtensionInstallLocation, IProfileAwareExtensionManagementService, DidChangeProfileExtensionsEvent } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/workbench/services/extensionManagement/browser/extensionEnablementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; @@ -64,6 +64,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { onDidInstallExtensions: new Emitter().event, onUninstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event, + onDidChangeProfileExtensions: new Emitter().event, }, }, null, null)); const extensionManagementService = instantiationService.createInstance(ExtensionManagementService); @@ -116,6 +117,7 @@ suite('ExtensionEnablementService Test', () => { const didInstallEvent = new Emitter(); const didUninstallEvent = new Emitter(); + const didChangeProfileExtensionsEvent = new Emitter(); const installed: ILocalExtension[] = []; setup(() => { @@ -128,7 +130,7 @@ suite('ExtensionEnablementService Test', () => { extensionManagementService: { onDidInstallExtensions: didInstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, - onDidChangeProfileExtensions: Event.None, + onDidChangeProfileExtensions: didChangeProfileExtensionsEvent.event, getInstalled: () => Promise.resolve(installed) }, }, null, null)); @@ -622,6 +624,38 @@ suite('ExtensionEnablementService Test', () => { assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByVirtualWorkspace); }); + test('test enable a remote workspace extension and local ui extension that is a dependency of remote', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); + const localUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + const target = aLocalExtension2('pub.b', { main: 'main.js', extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.b`).with({ scheme: 'vscode-remote' }) }); + testObject = new TestExtensionEnablementService(instantiationService); + + installed.push(localUIExtension, remoteUIExtension, target); + await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); + await testObject.setEnablement([target, localUIExtension], EnablementState.EnabledGlobally); + assert.ok(testObject.isEnabled(target)); + assert.ok(testObject.isEnabled(localUIExtension)); + assert.strictEqual(testObject.getEnablementState(target), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); + }); + + test('test enable a remote workspace extension also enables its dependency in local', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); + const localUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); + const remoteUIExtension = aLocalExtension2('pub.a', { main: 'main.js', extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); + const target = aLocalExtension2('pub.b', { main: 'main.js', extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.b`).with({ scheme: 'vscode-remote' }) }); + testObject = new TestExtensionEnablementService(instantiationService); + + installed.push(localUIExtension, remoteUIExtension, target); + await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); + await testObject.setEnablement([target], EnablementState.EnabledGlobally); + assert.ok(testObject.isEnabled(target)); + assert.ok(testObject.isEnabled(localUIExtension)); + assert.strictEqual(testObject.getEnablementState(target), EnablementState.EnabledGlobally); + assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); + }); + test('test canChangeEnablement return false when extension is disabled in virtual workspace', () => { const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); @@ -950,6 +984,22 @@ suite('ExtensionEnablementService Test', () => { assert.deepStrictEqual((target.args[0][0][1]).identifier, { id: 'pub.c' }); }); + test('test adding an extension that was disabled', async () => { + const extension = aLocalExtension('pub.a'); + installed.push(extension); + testObject = new TestExtensionEnablementService(instantiationService); + await testObject.setEnablement([extension], EnablementState.DisabledGlobally); + + const target = sinon.spy(); + testObject.onEnablementChanged(target); + didChangeProfileExtensionsEvent.fire({ added: [extension], removed: [] }); + + assert.ok(!testObject.isEnabled(extension)); + assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledGlobally); + assert.strictEqual(target.args[0][0].length, 1); + assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); + }); + }); function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index c9f5de7f2e4..acdf6ca2308 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -51,8 +51,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, ) { super( @@ -69,11 +69,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten extensionManifestPropertiesService, webExtensionsScannerService, logService, - remoteAgentService + remoteAgentService, + lifecycleService ); // Initialize installed extensions first and do it only after workbench is ready - this._lifecycleService.when(LifecyclePhase.Ready).then(async () => { + lifecycleService.when(LifecyclePhase.Ready).then(async () => { await this._userDataInitializationService.initializeInstalledExtensions(this._instantiationService); this._initialize(); }); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 966a2f9d8dc..76589d23182 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -34,9 +34,9 @@ import { URI } from 'vs/base/common/uri'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ApiProposalName, allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals'; -import { forEach } from 'vs/base/common/collections'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -188,6 +188,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IWebExtensionsScannerService protected readonly _webExtensionsScannerService: IWebExtensionsScannerService, @ILogService protected readonly _logService: ILogService, @IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService, + @ILifecycleService private readonly _lifecycleService: ILifecycleService, ) { super(); @@ -255,6 +256,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id])); } })); + + this._register(this._lifecycleService.onDidShutdown(() => { + this.stopExtensionHosts(); + })); } private _getExtensionKind(extensionDescription: IExtensionDescription): ExtensionKind[] { @@ -540,23 +545,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise { - const toAdd: IExtensionDescription[] = []; - for (let i = 0, len = _toAdd.length; i < len; i++) { - const extension = _toAdd[i]; - - const extensionDescription = await this._scanSingleExtension(extension); - if (!extensionDescription) { - // could not scan extension... - continue; - } - - if (!this.canAddExtension(extensionDescription)) { - continue; - } - - toAdd.push(extensionDescription); - } - let toRemove: IExtensionDescription[] = []; for (let i = 0, len = _toRemove.length; i < len; i++) { const extensionOrId = _toRemove[i]; @@ -581,6 +569,23 @@ export abstract class AbstractExtensionService extends Disposable implements IEx toRemove.push(extensionDescription); } + const toAdd: IExtensionDescription[] = []; + for (let i = 0, len = _toAdd.length; i < len; i++) { + const extension = _toAdd[i]; + + const extensionDescription = await this._scanSingleExtension(extension); + if (!extensionDescription) { + // could not scan extension... + continue; + } + + if (!this._canAddExtension(extensionDescription, toRemove)) { + continue; + } + + toAdd.push(extensionDescription); + } + if (toAdd.length === 0 && toRemove.length === 0) { return; } @@ -637,15 +642,19 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } public canAddExtension(extension: IExtensionDescription): boolean { - const existing = this._registry.getExtensionDescription(extension.identifier); - if (existing) { - // this extension is already running (most likely at a different version) - return false; - } + return this._canAddExtension(extension, []); + } - // Check if extension is renamed - if (extension.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.uuid)) { - return false; + private _canAddExtension(extension: IExtensionDescription, extensionsBeingRemoved: IExtensionDescription[]): boolean { + // (Also check for renamed extensions) + const existing = this._registry.getExtensionDescriptionByIdOrUUID(extension.identifier, extension.id); + if (existing) { + // This extension is already known (most likely at a different version) + // so it cannot be added again unless it is removed first + const isBeingRemoved = extensionsBeingRemoved.some((extensionDescription) => ExtensionIdentifier.equals(extension.identifier, extensionDescription.identifier)); + if (!isBeingRemoved) { + return false; + } } const extensionKind = this._getExtensionKind(extension); @@ -661,7 +670,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public canRemoveExtension(extension: IExtensionDescription): boolean { const extensionDescription = this._registry.getExtensionDescription(extension.identifier); if (!extensionDescription) { - // ignore removing an extension which is not running + // Can't remove an extension that is unknown! return false; } @@ -819,8 +828,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx previouslyActivatedExtensionIds.push(value); }); - for (const manager of this._extensionHostManagers) { - manager.dispose(); + // See https://github.com/microsoft/vscode/issues/152204 + // Dispose extension hosts in reverse creation order because the local extension host + // might be critical in sustaining a connection to the remote extension host + for (let i = this._extensionHostManagers.length - 1; i >= 0; i--) { + this._extensionHostManagers[i].dispose(); } this._extensionHostManagers = []; this._extensionHostActiveExtensions = new Map(); @@ -858,7 +870,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } const extensionHostId = String(++this._lastExtensionHostId); - const processManager: IExtensionHostManager = createExtensionHostManager(this._instantiationService, extensionHostId, extensionHost, isInitialStart, initialActivationEvents, this._acquireInternalAPI()); + const processManager: IExtensionHostManager = this._doCreateExtensionHostManager(extensionHostId, extensionHost, isInitialStart, initialActivationEvents); processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)); processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ @@ -870,6 +882,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return processManager; } + protected _doCreateExtensionHostManager(extensionHostId: string, extensionHost: IExtensionHost, isInitialStart: boolean, initialActivationEvents: string[]): IExtensionHostManager { + return createExtensionHostManager(this._instantiationService, extensionHostId, extensionHost, isInitialStart, initialActivationEvents, this._acquireInternalAPI()); + } + private _onExtensionHostCrashOrExit(extensionHost: IExtensionHostManager, code: number, signal: string | null): void { // Unexpected termination @@ -1462,9 +1478,9 @@ class ProposedApiController { // NEW world - product.json spells out what proposals each extension can use if (productService.extensionEnabledApiProposals) { - forEach(productService.extensionEnabledApiProposals, entry => { - const key = ExtensionIdentifier.toKey(entry.key); - const proposalNames = entry.value.filter(name => { + for (const [k, value] of Object.entries(productService.extensionEnabledApiProposals)) { + const key = ExtensionIdentifier.toKey(k); + const proposalNames = value.filter(name => { if (!allApiProposals[name]) { _logService.warn(`Via 'product.json#extensionEnabledApiProposals' extension '${key}' wants API proposal '${name}' but that proposal DOES NOT EXIST. Likely, the proposal has been finalized (check 'vscode.d.ts') or was abandoned.`); return false; @@ -1472,7 +1488,7 @@ class ProposedApiController { return true; }); this._productEnabledExtensions.set(key, proposalNames); - }); + } } } diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 001c0141a27..0277a064de7 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -68,19 +68,16 @@ export class ExtensionDescriptionRegistry { } public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult { - if (toAdd.length > 0) { - this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); - } + // It is possible that an extension is removed, only to be added again at a different version + // so we will first handle removals + this._extensionDescriptions = removeExtensions(this._extensionDescriptions, toRemove); + + // Then, handle the extensions to add + this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); // Immediately remove looping extensions! const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions); - toRemove = toRemove.concat(looping.map(ext => ext.identifier)); - - if (toRemove.length > 0) { - const toRemoveSet = new Set(); - toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); - this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); - } + this._extensionDescriptions = removeExtensions(this._extensionDescriptions, looping.map(ext => ext.identifier)); this._initialize(); this._onDidChange.fire(undefined); @@ -194,6 +191,22 @@ export class ExtensionDescriptionRegistry { const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId)); return extension ? extension : undefined; } + + public getExtensionDescriptionByUUID(uuid: string): IExtensionDescription | undefined { + for (const extensionDescription of this._extensionsArr) { + if (extensionDescription.uuid === uuid) { + return extensionDescription; + } + } + return undefined; + } + + public getExtensionDescriptionByIdOrUUID(extensionId: ExtensionIdentifier | string, uuid: string | undefined): IExtensionDescription | undefined { + return ( + this.getExtensionDescription(extensionId) + ?? (uuid ? this.getExtensionDescriptionByUUID(uuid) : undefined) + ); + } } const enum SortBucket { @@ -226,3 +239,9 @@ function extensionCmp(a: IExtensionDescription, b: IExtensionDescription): numbe } return 0; } + +function removeExtensions(arr: IExtensionDescription[], toRemove: ExtensionIdentifier[]): IExtensionDescription[] { + const toRemoveSet = new Set(); + toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); + return arr.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); +} diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index c59f40ac8f3..7366addd3dd 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -131,7 +131,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager { }; this._telemetryService.publicLog2('extensionHostStartup', startingTelemetryEvent); - this._proxy = this._extensionHost.start()!.then( + this._proxy = this._extensionHost.start().then( (protocol) => { this._hasStarted = true; diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 8ab83032797..17d96c5614d 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -157,7 +157,7 @@ export interface IExtensionHost { readonly extensions: ExtensionHostExtensions; readonly onExit: Event<[number, string | null]>; - start(): Promise | null; + start(): Promise; getInspectPort(): number | undefined; enableInspectPort(): Promise; dispose(): void; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 434922068c0..c1c24173042 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -14,6 +14,7 @@ export const allApiProposals = Object.freeze({ contribMenuBarHome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMenuBarHome.d.ts', contribMergeEditorToolbar: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts', contribRemoteHelp: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribRemoteHelp.d.ts', + contribShareMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts', contribViewsRemote: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsRemote.d.ts', contribViewsWelcome: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribViewsWelcome.d.ts', customEditorMove: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.customEditorMove.d.ts', diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 7ca0c510664..62eea683c18 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -28,7 +28,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { createMessageOfType, isMessageOfType, MessageType, IExtensionHostInitData, UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostExtensions, ExtensionHostLogFileName, IExtensionHost, RemoteRunningLocation } from 'vs/workbench/services/extensions/common/extensions'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output'; export interface IRemoteExtensionHostInitData { @@ -68,7 +67,6 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -82,8 +80,6 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { this._hasLostConnection = false; this._terminating = false; - this._register(this._lifecycleService.onDidShutdown(() => this.dispose())); - const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; } @@ -273,12 +269,16 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { if (this._protocol) { // Send the extension host a request to terminate itself // (graceful termination) + // setTimeout(() => { + // console.log(`SENDING TERMINATE TO REMOTE EXT HOST!`); const socket = this._protocol.getSocket(); this._protocol.send(createMessageOfType(MessageType.Terminate)); this._protocol.sendDisconnect(); this._protocol.dispose(); + // this._protocol.drain(); socket.end(); this._protocol = null; + // }, 1000); } } } diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts deleted file mode 100644 index 2e4c745a9f8..00000000000 --- a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost'; -import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService'; - -export class NativeExtensionService extends ElectronExtensionService { - protected override _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { - if (runningLocation.kind === ExtensionHostKind.LocalProcess) { - return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); - } - return super._createExtensionHost(runningLocation, isInitialStart); - } -} - -registerSingleton(IExtensionService, NativeExtensionService); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index 9a989916aa6..3c267c40962 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -73,8 +73,8 @@ export abstract class ElectronExtensionService extends AbstractExtensionService @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @ILifecycleService private readonly _lifecycleService: ILifecycleService, @INativeHostService private readonly _nativeHostService: INativeHostService, @IHostService private readonly _hostService: IHostService, @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @@ -95,7 +95,8 @@ export abstract class ElectronExtensionService extends AbstractExtensionService extensionManifestPropertiesService, webExtensionsScannerService, logService, - remoteAgentService + remoteAgentService, + lifecycleService ); [this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled(); @@ -108,7 +109,7 @@ export abstract class ElectronExtensionService extends AbstractExtensionService // some editors require the extension host to restore // and this would result in a deadlock // see https://github.com/microsoft/vscode/issues/41322 - this._lifecycleService.when(LifecyclePhase.Ready).then(() => { + lifecycleService.when(LifecyclePhase.Ready).then(() => { // reschedule to ensure this runs after restoring viewlets, panels, and editors runWhenIdle(() => { this._initialize(); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index 7b517d22911..97eeb78e28e 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -35,7 +35,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { SerializedError } from 'vs/base/common/errors'; +import { CancellationError, SerializedError } from 'vs/base/common/errors'; import { removeDangerousEnvVariables } from 'vs/base/common/processes'; import { StopWatch } from 'vs/base/common/stopwatch'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -164,7 +164,6 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { this._toDispose.add(this._onExit); this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); - this._toDispose.add(this._lifecycleService.onDidShutdown(() => this._terminate())); this._toDispose.add(this._extensionHostDebugService.onClose(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { this._nativeHostService.closeWindow(); @@ -178,10 +177,6 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { } public dispose(): void { - this._terminate(); - } - - private _terminate(): void { if (this._terminating) { return; } @@ -190,10 +185,10 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { this._toDispose.dispose(); } - public start(): Promise | null { + public start(): Promise { if (this._terminating) { // .terminate() was called - return null; + throw new CancellationError(); } if (!this._messageProtocol) { diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts similarity index 88% rename from src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts rename to src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts index 888db160609..35d0657b669 100644 --- a/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createServer, Server } from 'net'; +/* eslint-disable code-import-patterns */ +/* eslint-disable code-layering */ + import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -15,11 +17,12 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IPCExtHostConnection, writeExtHostConnection } from 'vs/workbench/services/extensions/common/extensionHostEnv'; import { createMessageOfType, MessageType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostProcess, ExtHostMessagePortCommunication, IExtHostCommunication, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; export class NativeLocalProcessExtensionHost extends SandboxLocalProcessExtensionHost { protected override async _start(): Promise { const canUseUtilityProcess = await this._extensionHostStarter.canUseUtilityProcess(); - if (canUseUtilityProcess && this._configurationService.getValue('extensions.experimental.useUtilityProcess')) { + if (canUseUtilityProcess && (this._configurationService.getValue('extensions.experimental.useUtilityProcess') || process.sandboxed)) { const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService)); return this._startWithCommunication(communication); } else { @@ -31,7 +34,7 @@ export class NativeLocalProcessExtensionHost extends SandboxLocalProcessExtensio interface INamedPipePreparedData { pipeName: string; - namedPipeServer: Server; + namedPipeServer: import('net').Server; } class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication { @@ -44,8 +47,9 @@ class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommun super(); } - prepare(): Promise { - return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => { + async prepare(): Promise { + const { createServer } = await import('net'); + return new Promise<{ pipeName: string; namedPipeServer: import('net').Server }>((resolve, reject) => { const pipeName = createRandomIPCHandle(); const namedPipeServer = createServer(); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts index ea51d7abc8f..58e5c5bf71c 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts @@ -4,10 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService'; +import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; export class SandboxExtensionService extends ElectronExtensionService { + protected override _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { + if (!process.sandboxed && runningLocation.kind === ExtensionHostKind.LocalProcess) { + // TODO@bpasero remove me once electron utility process has landed + return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation)); + } + return super._createExtensionHost(runningLocation, isInitialStart); + } } registerSingleton(IExtensionService, SandboxExtensionService); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index ecc2877518c..18d684cacaa 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -5,8 +5,34 @@ import * as assert from 'assert'; import { ExtensionService as BrowserExtensionService } from 'vs/workbench/services/extensions/browser/extensionService'; -import { ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService'; -import { ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { ExtensionKind, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtensionIdentifier, IExtension, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { createServices, TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { WorkspaceTrustEnablementService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; +import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { AbstractExtensionService, ExtensionRunningPreference } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { mock } from 'vs/base/test/common/mock'; +import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { @@ -87,3 +113,80 @@ suite('BrowserExtensionService', () => { assert.deepStrictEqual(BrowserExtensionService.pickRunningLocation(['workspace', 'web', 'ui'], true, true, ExtensionRunningPreference.None), ExtensionHostKind.Remote); }); }); + +suite('ExtensionService', () => { + + class MyTestExtensionService extends AbstractExtensionService { + public readonly order: string[] = []; + protected _pickExtensionHostKind(extensionId: ExtensionIdentifier, extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean, preference: ExtensionRunningPreference): ExtensionHostKind | null { + throw new Error('Method not implemented.'); + } + protected override _doCreateExtensionHostManager(extensionHostId: string, extensionHost: IExtensionHost, isInitialStart: boolean, initialActivationEvents: string[]): IExtensionHostManager { + const order = this.order; + order.push(`create ${extensionHostId}`); + return new class extends mock() { + override onDidExit = Event.None; + override onDidChangeResponsiveState = Event.None; + override dispose(): void { + order.push(`dispose ${extensionHostId}`); + } + override representsRunningLocation(runningLocation: ExtensionRunningLocation): boolean { + return extensionHost.runningLocation.equals(runningLocation); + } + }; + } + protected _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { + return new class extends mock() { + override runningLocation = runningLocation; + }; + } + protected _scanAndHandleExtensions(): Promise { + throw new Error('Method not implemented.'); + } + protected _scanSingleExtension(extension: IExtension): Promise | null> { + throw new Error('Method not implemented.'); + } + public _onExtensionHostExit(code: number): void { + throw new Error('Method not implemented.'); + } + } + + let disposables: DisposableStore; + let instantiationService: TestInstantiationService; + let extService: MyTestExtensionService; + + setup(() => { + disposables = new DisposableStore(); + instantiationService = createServices(disposables, [ + // custom + [IExtensionService, MyTestExtensionService], + // default + [ILifecycleService, TestLifecycleService], + [IWorkbenchExtensionManagementService, TestWorkbenchExtensionManagementService], + [INotificationService, TestNotificationService], + [IRemoteAgentService, TestRemoteAgentService], + [ILogService, NullLogService], + [IWebExtensionsScannerService, TestWebExtensionsScannerService], + [IExtensionManifestPropertiesService, ExtensionManifestPropertiesService], + [IConfigurationService, TestConfigurationService], + [IWorkspaceContextService, TestContextService], + [IProductService, { _serviceBrand: undefined, ...product }], + [IFileService, TestFileService], + [IWorkbenchExtensionEnablementService, TestWorkbenchExtensionEnablementService], + [ITelemetryService, NullTelemetryService], + [IEnvironmentService, TestEnvironmentService], + [IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService] + ]); + extService = instantiationService.get(IExtensionService); + }); + + teardown(() => { + disposables.dispose(); + }); + + test('issue #152204: Remote extension host not disposed after closing vscode client', async () => { + await extService.startExtensionHosts(); + extService.stopExtensionHosts(); + assert.deepStrictEqual(extService.order, (['create 1', 'create 2', 'create 3', 'dispose 3', 'dispose 2', 'dispose 1'])); + }); +}); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts index bbee18ae82b..d6843185c30 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts @@ -39,7 +39,7 @@ suite('ExtensionStorageMigration', () => { instantiationService.stub(IFileService, fileService); const environmentService = instantiationService.stub(IEnvironmentService, >{ userRoamingDataHome: ROOT, workspaceStorageHome }); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new NullLogService())); - instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); instantiationService.stub(IExtensionStorageService, instantiationService.createInstance(ExtensionStorageService)); }); diff --git a/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts new file mode 100644 index 00000000000..8249f569082 --- /dev/null +++ b/src/vs/workbench/services/extensions/test/common/extensionDescriptionRegistry.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier, IExtensionDescription, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; + +suite('ExtensionDescriptionRegistry', () => { + test('allow removing and adding the same extension at a different version', () => { + const idA = new ExtensionIdentifier('a'); + const extensionA1 = desc(idA, '1.0.0'); + const extensionA2 = desc(idA, '2.0.0'); + + const registry = new ExtensionDescriptionRegistry([extensionA1]); + registry.deltaExtensions([extensionA2], [idA]); + + assert.deepStrictEqual(registry.getAllExtensionDescriptions(), [extensionA2]); + }); + + function desc(id: ExtensionIdentifier, version: string, activationEvents: string[] = ['*']): IExtensionDescription { + return { + name: id.value, + publisher: 'test', + version: '0.0.0', + engines: { vscode: '^1.0.0' }, + identifier: id, + extensionLocation: URI.parse(`nothing://nowhere`), + isBuiltin: false, + isUnderDevelopment: false, + isUserBuiltin: false, + activationEvents, + main: 'index.js', + targetPlatform: TargetPlatform.UNDEFINED, + extensionDependencies: [] + }; + } +}); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index e0fb71b15ed..6f73bd54add 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -993,6 +993,10 @@ export class HistoryService extends Disposable implements IHistoryService { try { const entriesParsed: ISerializedEditorHistoryEntry[] = JSON.parse(entriesRaw); for (const entryParsed of entriesParsed) { + if (!entryParsed.editor || !entryParsed.editor.resource) { + continue; // unexpected data format + } + try { entries.push({ ...entryParsed.editor, diff --git a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts index 3dd5d8dbc16..7125f82440e 100644 --- a/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts +++ b/src/vs/workbench/services/integrity/electron-sandbox/integrityService.ts @@ -33,7 +33,7 @@ class IntegrityStorage { } private _read(): IStorageData | null { - const jsonValue = this.storageService.get(IntegrityStorage.KEY, StorageScope.PROFILE); + const jsonValue = this.storageService.get(IntegrityStorage.KEY, StorageScope.APPLICATION); if (!jsonValue) { return null; } @@ -50,7 +50,7 @@ class IntegrityStorage { set(data: IStorageData | null): void { this.value = data; - this.storageService.store(IntegrityStorage.KEY, JSON.stringify(this.value), StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.store(IntegrityStorage.KEY, JSON.stringify(this.value), StorageScope.APPLICATION, StorageTarget.MACHINE); } } diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 40f41611139..a72466b917a 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -20,6 +20,7 @@ import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/co import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -33,7 +34,8 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IProductService private readonly productService: IProductService, @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IIntegrityService private readonly integrityService: IIntegrityService ) { } async openReporter(dataOverrides: Partial = {}): Promise { @@ -82,6 +84,14 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { // Ignore } + // air on the side of caution and have false be the default + let isUnsupported = false; + try { + isUnsupported = !(await this.integrityService.isPure()).isPure; + } catch (e) { + // Ignore + } + const theme = this.themeService.getColorTheme(); const issueReporterData: IssueReporterData = Object.assign({ styles: getIssueReporterStyles(theme), @@ -89,6 +99,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { enabledExtensions: extensionData, experiments: experiments?.join('\n'), restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(), + isUnsupported, githubAccessToken, }, dataOverrides); return this.issueService.openReporter(issueReporterData); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 2317c4e28fa..fe4650f771f 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -50,7 +50,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { dirname } from 'vs/base/common/resources'; import { getAllUnboundCommands } from 'vs/workbench/services/keybinding/browser/unboundCommands'; import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface ContributedKeyBinding { command: string; @@ -738,10 +738,15 @@ class UserKeybindings extends Disposable { } })); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => { - this.watch(); - e.join(this.reload().then(() => this.reloadConfigurationScheduler.schedule())); - })); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenCurrentProfieChanged(e)))); + } + + private async whenCurrentProfieChanged(e: DidChangeUserDataProfileEvent): Promise { + if (e.preserveData) { + await this.fileService.copy(e.previous.keybindingsResource, e.profile.keybindingsResource); + } + this.watch(); + this.reloadConfigurationScheduler.schedule(); } private watch(): void { diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index 66b5e7fa8a8..a20bd496d4b 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -66,7 +66,8 @@ suite('KeybindingsEditing', () => { const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'eol': '\n' }); - userDataProfileService = new UserDataProfileService(new UserDataProfilesService(environmentService, fileService, logService).defaultProfile); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); + userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); instantiationService = workbenchInstantiationService({ fileService: () => fileService, diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 32ade1ac946..1a768532b79 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -24,11 +24,15 @@ export class NotificationService extends Disposable implements INotificationServ private readonly _onDidRemoveNotification = this._register(new Emitter()); readonly onDidRemoveNotification = this._onDidRemoveNotification.event; + private readonly _onDidChangeDoNotDisturbMode = this._register(new Emitter()); + readonly onDidChangeDoNotDisturbMode = this._onDidChangeDoNotDisturbMode.event; + constructor( @IStorageService private readonly storageService: IStorageService ) { super(); + this.updateDoNotDisturbFilters(); this.registerListeners(); } @@ -58,10 +62,44 @@ export class NotificationService extends Disposable implements INotificationServ })); } - setFilter(filter: NotificationsFilter): void { + //#region Do not disturb mode + + static readonly DND_SETTINGS_KEY = 'notifications.doNotDisturbMode'; + + private _doNotDisturbMode = this.storageService.getBoolean(NotificationService.DND_SETTINGS_KEY, StorageScope.APPLICATION, false); + + get doNotDisturbMode() { + return this._doNotDisturbMode; + } + + set doNotDisturbMode(enabled: boolean) { + if (this._doNotDisturbMode === enabled) { + return; // no change + } + + this.storageService.store(NotificationService.DND_SETTINGS_KEY, enabled, StorageScope.APPLICATION, StorageTarget.MACHINE); + this._doNotDisturbMode = enabled; + + // Toggle via filter + this.updateDoNotDisturbFilters(); + + // Events + this._onDidChangeDoNotDisturbMode.fire(); + } + + private updateDoNotDisturbFilters(): void { + let filter: NotificationsFilter; + if (this._doNotDisturbMode) { + filter = NotificationsFilter.ERROR; + } else { + filter = NotificationsFilter.OFF; + } + this.model.setFilter(filter); } + //#endregion + info(message: NotificationMessage | NotificationMessage[]): void { if (Array.isArray(message)) { message.forEach(m => this.info(m)); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index e4bffd568d0..8172f41fa24 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -70,7 +70,7 @@ export class ProgressService extends Disposable implements IProgressService { switch (location) { case ProgressLocation.Notification: - return this.withNotificationProgress({ ...options, location }, task, onDidCancel); + return this.withNotificationProgress({ ...options, location, silent: this.notificationService.doNotDisturbMode }, task, onDidCancel); case ProgressLocation.Window: if ((options as IProgressWindowOptions).command) { // Window progress with command get's shown in the status bar diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 5b8449ffd83..6d037174bed 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -223,7 +223,7 @@ export interface TextSearchOptions extends SearchOptions { } /** - * Represents the severiry of a TextSearchComplete message. + * Represents the severity of a TextSearchComplete message. */ export enum TextSearchCompleteMessageType { Information = 1, diff --git a/src/vs/workbench/services/storage/browser/storageService.ts b/src/vs/workbench/services/storage/browser/storageService.ts index 694d8317a1f..e267855723c 100644 --- a/src/vs/workbench/services/storage/browser/storageService.ts +++ b/src/vs/workbench/services/storage/browser/storageService.ts @@ -23,7 +23,7 @@ export class BrowserStorageService extends AbstractStorageService { private applicationStorage: IStorage | undefined; private applicationStorageDatabase: IIndexedDBStorageDatabase | undefined; - private readonly applicationStoragePromise = new DeferredPromise<{ indededDb: IIndexedDBStorageDatabase; storage: IStorage }>(); + private readonly applicationStoragePromise = new DeferredPromise<{ indexedDb: IIndexedDBStorageDatabase; storage: IStorage }>(); private profileStorage: IStorage | undefined; private profileStorageDatabase: IIndexedDBStorageDatabase | undefined; @@ -92,7 +92,7 @@ export class BrowserStorageService extends AbstractStorageService { this.updateIsNew(this.applicationStorage); - this.applicationStoragePromise.complete({ indededDb: applicationStorageIndexedDB, storage: this.applicationStorage }); + this.applicationStoragePromise.complete({ indexedDb: applicationStorageIndexedDB, storage: this.applicationStorage }); } private async createProfileStorage(profile: IUserDataProfile): Promise { @@ -110,22 +110,24 @@ export class BrowserStorageService extends AbstractStorageService { // avoid creating the storage library a second time on // the same DB. - const { indededDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p; + const { indexedDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p; this.profileStorageDatabase = applicationStorageIndexedDB; this.profileStorage = applicationStorage; + + this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key))); } else { const profileStorageIndexedDB = await IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.PROFILE), broadcastChanges: true }, this.logService); this.profileStorageDatabase = this.profileStorageDisposables.add(profileStorageIndexedDB); this.profileStorage = this.profileStorageDisposables.add(new Storage(this.profileStorageDatabase)); + + this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key))); + + await this.profileStorage.init(); + + this.updateIsNew(this.profileStorage); } - - this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key))); - - await this.profileStorage.init(); - - this.updateIsNew(this.profileStorage); } private async createWorkspaceStorage(): Promise { @@ -166,7 +168,7 @@ export class BrowserStorageService extends AbstractStorageService { } protected async switchToProfile(toProfile: IUserDataProfile, preserveData: boolean): Promise { - if (this.profileStorageProfile && !this.canSwitchProfile(this.profileStorageProfile, toProfile)) { + if (!this.canSwitchProfile(this.profileStorageProfile, toProfile)) { return; } diff --git a/src/vs/workbench/services/storage/test/browser/storageService.test.ts b/src/vs/workbench/services/storage/test/browser/storageService.test.ts index 58d1c19d3cb..4205c92d7a8 100644 --- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts +++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts @@ -16,9 +16,10 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { NullLogService } from 'vs/platform/log/common/log'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { createSuite } from 'vs/platform/storage/test/common/storageService.test'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { BrowserStorageService, IndexedDBStorageDatabase } from 'vs/workbench/services/storage/browser/storageService'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; async function createStorageService(): Promise<[DisposableStore, BrowserStorageService]> { const disposables = new DisposableStore(); @@ -45,7 +46,7 @@ async function createStorageService(): Promise<[DisposableStore, BrowserStorageS extensionsResource: joinPath(inMemoryExtraProfileRoot, 'extensionsResource') }; - const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile), logService)); + const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile, new UserDataProfilesService(TestEnvironmentService, fileService, logService)), logService)); await storageService.initialize(); diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 892f318d293..3ad179570b6 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -11,7 +11,6 @@ import { ILoggerService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender'; -import { WebAppInsightsAppender } from 'vs/platform/telemetry/browser/appInsightsAppender'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; @@ -38,17 +37,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey && productService.aiConfig?.ariaKey) { + if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey) { // If remote server is present send telemetry through that, else use the client side appender - const internalTesting = configurationService.getValue('telemetry.internalTesting'); const appenders = []; - if (internalTesting || productService.aiConfig?.preferAria) { - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig?.ariaKey); - appenders.push(telemetryProvider); - } else { - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey); - appenders.push(telemetryProvider); - } + const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig?.ariaKey); + appenders.push(telemetryProvider); appenders.push(new TelemetryLogAppender(loggerService, environmentService)); const config: ITelemetryServiceConfig = { appenders, diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 470a3e57168..3b1578dc366 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -3,26 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { VSBuffer } from 'vs/base/common/buffer'; import { Disposable } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ILocalExtension, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { EXTENSIONS_RESOURCE_NAME, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceContextService, IWorkspaceIdentifier, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IExtensionManagementServerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IUserDataProfileManagementService, IUserDataProfileService, IUserDataProfileTemplate, PROFILES_CATEGORY } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class UserDataProfileManagementService extends Disposable implements IUserDataProfileManagementService { readonly _serviceBrand: undefined; @@ -30,53 +20,27 @@ export class UserDataProfileManagementService extends Disposable implements IUse constructor( @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IFileService private readonly fileService: IFileService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService, - @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IHostService private readonly hostService: IHostService, @IDialogService private readonly dialogService: IDialogService, - @IProgressService private readonly progressService: IProgressService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService ) { super(); + this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); } - private async checkAndCreateExtensionsProfileResource(): Promise { - if (this.userDataProfileService.currentProfile.extensionsResource) { - return this.userDataProfileService.currentProfile.extensionsResource; + private onDidChangeProfiles(e: DidChangeProfilesEvent): void { + if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + return; } - if (!this.userDataProfilesService.defaultProfile.extensionsResource) { - // Extensions profile is not yet created for default profile, create it now - return this.createDefaultExtensionsProfile(joinPath(this.userDataProfilesService.defaultProfile.location, EXTENSIONS_RESOURCE_NAME)); - } - throw new Error('Invalid Profile'); } - async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise { - const workspaceIdentifier = this.getWorkspaceIdentifier(); - if (!workspaceIdentifier) { - throw new Error(localize('cannotCreateProfileInEmptyWorkbench', "Cannot create a profile in an empty workspace")); - } - const promises: Promise[] = []; - const newProfile = this.userDataProfilesService.newProfile(name, useDefaultFlags); - await this.fileService.createFolder(newProfile.location); - const extensionsProfileResourcePromise = this.checkAndCreateExtensionsProfileResource(); - promises.push(extensionsProfileResourcePromise); - if (fromExisting) { - // Storage copy is handled by storage service while entering profile - promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.settingsResource, newProfile.settingsResource)); - promises.push((async () => this.fileService.copy(await extensionsProfileResourcePromise, newProfile.extensionsResource))()); - promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.keybindingsResource, newProfile.keybindingsResource)); - promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.tasksResource, newProfile.tasksResource)); - promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.snippetsHome, newProfile.snippetsHome)); - } - await Promise.allSettled(promises); - const createdProfile = await this.userDataProfilesService.createProfile(newProfile, workspaceIdentifier); - await this.enterProfile(createdProfile, !!fromExisting); + async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise { + const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier()); + await this.enterProfile(profile, !!fromExisting); + return profile; } async removeProfile(profile: IUserDataProfile): Promise { @@ -89,18 +53,11 @@ export class UserDataProfileManagementService extends Disposable implements IUse if (profile.id === this.userDataProfileService.currentProfile.id) { throw new Error(localize('cannotDeleteCurrentProfile', "Cannot delete the current profile")); } - const defaultExtensionsResourceToDelete = this.userDataProfilesService.profiles.length === 2 ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined; await this.userDataProfilesService.removeProfile(profile); - if (defaultExtensionsResourceToDelete) { - try { await this.fileService.del(defaultExtensionsResourceToDelete); } catch (error) { /* ignore */ } - } } async switchProfile(profile: IUserDataProfile): Promise { const workspaceIdentifier = this.getWorkspaceIdentifier(); - if (!workspaceIdentifier) { - throw new Error(localize('cannotSwitchProfileInEmptyWorkbench', "Cannot switch a profile in an empty workspace")); - } if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { throw new Error(`Profile ${profile.name} does not exist`); } @@ -111,34 +68,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse await this.enterProfile(profile, false); } - async createAndEnterProfileFromTemplate(name: string, template: IUserDataProfileTemplate, useDefaultFlags: UseDefaultProfileFlags): Promise { - const workspaceIdentifier = this.getWorkspaceIdentifier(); - if (!workspaceIdentifier) { - throw new Error(localize('cannotCreateProfileInEmptyWorkbench', "Cannot create a profile in an empty workspace")); - } - const profile = await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('profiles.creating', "{0}: Creating...", PROFILES_CATEGORY), - }, async progress => { - const promises: Promise[] = []; - const newProfile = this.userDataProfilesService.newProfile(name, useDefaultFlags); - await this.fileService.createFolder(newProfile.location); - if (template.globalState) { - // todo: create global state - } - if (template.settings) { - promises.push(this.fileService.writeFile(newProfile.settingsResource, VSBuffer.fromString(template.settings))); - } - if (template.extensions && newProfile.extensionsResource) { - promises.push(this.fileService.writeFile(newProfile.extensionsResource, VSBuffer.fromString(template.extensions))); - } - await Promise.allSettled(promises); - return this.userDataProfilesService.createProfile(newProfile, workspaceIdentifier); - }); - await this.enterProfile(profile, false); - } - - private getWorkspaceIdentifier(): ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier | undefined { + private getWorkspaceIdentifier(): WorkspaceIdentifier { const workspace = this.workspaceContextService.getWorkspace(); switch (this.workspaceContextService.getWorkbenchState()) { case WorkbenchState.FOLDER: @@ -146,14 +76,14 @@ export class UserDataProfileManagementService extends Disposable implements IUse case WorkbenchState.WORKSPACE: return { configPath: workspace.configuration!, id: workspace.id }; } - return undefined; + return 'empty-window'; } - private async enterProfile(profile: IUserDataProfile, preserveData: boolean): Promise { + private async enterProfile(profile: IUserDataProfile, preserveData: boolean, reloadMessage?: string): Promise { if (this.environmentService.remoteAuthority) { const result = await this.dialogService.confirm({ type: 'info', - message: localize('reload message', "Switching a profile requires reloading VS Code."), + message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), primaryButton: localize('reload button', "&&Reload"), }); if (result.confirmed) { @@ -166,15 +96,6 @@ export class UserDataProfileManagementService extends Disposable implements IUse await this.userDataProfileService.updateCurrentProfile(profile, preserveData); await this.extensionService.startExtensionHosts(); } - - private async createDefaultExtensionsProfile(extensionsProfileResource: URI): Promise { - try { await this.fileService.del(extensionsProfileResource); } catch (error) { /* ignore */ } - const extensionManagementService = this.extensionManagementServerService.localExtensionManagementServer?.extensionManagementService ?? this.extensionManagementService; - const userExtensions = await extensionManagementService.getInstalled(ExtensionType.User); - const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(userExtensions.map(async e => ([e, await this.extensionManagementService.getMetadata(e)]))); - await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, extensionsProfileResource); - return extensionsProfileResource; - } } registerSingleton(IUserDataProfileManagementService, UserDataProfileManagementService); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 24d3f973e0d..bed76364445 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -10,7 +10,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyDefinedExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IsWebContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; @@ -31,15 +31,13 @@ export const IUserDataProfileManagementService = createDecorator; - createAndEnterProfileFromTemplate(name: string, template: IUserDataProfileTemplate, useDefaultFlags?: UseDefaultProfileFlags): Promise; + createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise; removeProfile(profile: IUserDataProfile): Promise; switchProfile(profile: IUserDataProfile): Promise; } export interface IUserDataProfileTemplate { - readonly name?: string; readonly settings?: string; readonly globalState?: string; readonly extensions?: string; @@ -49,7 +47,6 @@ export function isUserDataProfileTemplate(thing: unknown): thing is IUserDataPro const candidate = thing as IUserDataProfileTemplate | undefined; return !!(candidate && typeof candidate === 'object' - && (isUndefined(candidate.name) || typeof candidate.name === 'string') && (isUndefined(candidate.settings) || typeof candidate.settings === 'string') && (isUndefined(candidate.globalState) || typeof candidate.globalState === 'string') && (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string')); @@ -57,11 +54,12 @@ export function isUserDataProfileTemplate(thing: unknown): thing is IUserDataPro export type ProfileCreationOptions = { readonly skipComments: boolean }; -export const IUserDataProfileWorkbenchService = createDecorator('IUserDataProfileWorkbenchService'); -export interface IUserDataProfileWorkbenchService { +export const IUserDataProfileImportExportService = createDecorator('IUserDataProfileImportExportService'); +export interface IUserDataProfileImportExportService { readonly _serviceBrand: undefined; - createProfile(options?: ProfileCreationOptions): Promise; + exportProfile(options?: ProfileCreationOptions): Promise; + importProfile(profile: IUserDataProfileTemplate): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } @@ -75,4 +73,4 @@ export const PROFILES_TTILE = { value: localize('settings profiles', "Settings P export const PROFILES_CATEGORY = PROFILES_TTILE.value; export const PROFILE_EXTENSION = 'code-profile'; export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }]; -export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); +export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileWorkbenchService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts similarity index 60% rename from src/vs/workbench/services/userDataProfile/common/userDataProfileWorkbenchService.ts rename to src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts index e4aec98fb84..8b9b4b76309 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileWorkbenchService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts @@ -10,10 +10,11 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { ExtensionsProfile } from 'vs/workbench/services/userDataProfile/common/extensionsProfile'; import { GlobalStateProfile } from 'vs/workbench/services/userDataProfile/common/globalStateProfile'; -import { IUserDataProfileTemplate, IUserDataProfileWorkbenchService, PROFILES_CATEGORY } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileTemplate, IUserDataProfileImportExportService, PROFILES_CATEGORY, IUserDataProfileManagementService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { SettingsProfile } from 'vs/workbench/services/userDataProfile/common/settingsProfile'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -export class UserDataProfileWorkbenchService implements IUserDataProfileWorkbenchService { +export class UserDataProfileImportExportService implements IUserDataProfileImportExportService { readonly _serviceBrand: undefined; @@ -24,14 +25,16 @@ export class UserDataProfileWorkbenchService implements IUserDataProfileWorkbenc constructor( @IInstantiationService instantiationService: IInstantiationService, @IProgressService private readonly progressService: IProgressService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, + @IQuickInputService private readonly quickInputService: IQuickInputService, ) { this.settingsProfile = instantiationService.createInstance(SettingsProfile); this.globalStateProfile = instantiationService.createInstance(GlobalStateProfile); this.extensionsProfile = instantiationService.createInstance(ExtensionsProfile); } - async createProfile(options?: { skipComments: boolean }): Promise { + async exportProfile(options?: { skipComments: boolean }): Promise { const settings = await this.settingsProfile.getProfileContent(options); const globalState = await this.globalStateProfile.getProfileContent(); const extensions = await this.extensionsProfile.getProfileContent(); @@ -42,6 +45,34 @@ export class UserDataProfileWorkbenchService implements IUserDataProfileWorkbenc }; } + async importProfile(profileTemplate: IUserDataProfileTemplate): Promise { + const name = await this.quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('save profile as', "Create from Current Profile..."), + }); + if (!name) { + return undefined; + } + + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY), + }, async progress => { + await this.userDataProfileManagementService.createAndEnterProfile(name); + if (profileTemplate.settings) { + await this.settingsProfile.applyProfile(profileTemplate.settings); + } + if (profileTemplate.globalState) { + await this.globalStateProfile.applyProfile(profileTemplate.globalState); + } + if (profileTemplate.extensions) { + await this.extensionsProfile.applyProfile(profileTemplate.extensions); + } + }); + + this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY)); + } + async setProfile(profile: IUserDataProfileTemplate): Promise { await this.progressService.withProgress({ location: ProgressLocation.Notification, @@ -62,4 +93,4 @@ export class UserDataProfileWorkbenchService implements IUserDataProfileWorkbenc } -registerSingleton(IUserDataProfileWorkbenchService, UserDataProfileWorkbenchService); +registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index e29eefe0e24..57c6e4161a2 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -6,7 +6,7 @@ import { Promises } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class UserDataProfileService extends Disposable implements IUserDataProfileService { @@ -19,9 +19,18 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi private _currentProfile: IUserDataProfile; get currentProfile(): IUserDataProfile { return this._currentProfile; } - constructor(currentProfile: IUserDataProfile) { + constructor(currentProfile: IUserDataProfile, userDataProfilesService: IUserDataProfilesService) { super(); this._currentProfile = currentProfile; + this._register(userDataProfilesService.onDidChangeProfiles(() => { + /** + * If the current profile is default profile, then reset it because, + * In Desktop the extensions resource will be set/unset in the default profile when profiles are changed. + */ + if (this._currentProfile.isDefault) { + this._currentProfile = userDataProfilesService.defaultProfile; + } + })); } async updateCurrentProfile(userDataProfile: IUserDataProfile, preserveData: boolean): Promise { diff --git a/src/vs/workbench/services/workingCopy/common/abstractFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/abstractFileWorkingCopyManager.ts index 4534377d02e..5cc96b59be1 100644 --- a/src/vs/workbench/services/workingCopy/common/abstractFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/abstractFileWorkingCopyManager.ts @@ -73,7 +73,7 @@ export abstract class BaseFileWorkingCopyManager this.remove(resource))); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 55387453019..3d2df342bae 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -126,7 +126,7 @@ export interface IFileWorkingCopySaveAsOptions extends ISaveOptions { /** * Optional target resource to suggest to the user in case - * no taget resource is provided to save to. + * no target resource is provided to save to. */ suggestedTarget?: URI; } diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts index d7c4f83dcc0..d0bc02b3e64 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts @@ -625,7 +625,7 @@ export class StoredFileWorkingCopyManager protected override remove(resource: URI): boolean { const removed = super.remove(resource); - // Dispose any exsting working copy listeners + // Dispose any existing working copy listeners const workingCopyListener = this.mapResourceToWorkingCopyListeners.get(resource); if (workingCopyListener) { dispose(workingCopyListener); diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts index 7640a135058..f985afb7dc9 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts @@ -236,7 +236,7 @@ export class UntitledFileWorkingCopyManager { return { path: '/default', profileName: 'Default', isDefault: true }; } async getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise { return '/default'; } async getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise { return []; } + getDefaultIcon(): TerminalIcon & ThemeIcon { return Codicon.terminal; } async getEnvironment(): Promise { return env; } getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; } getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; } @@ -1928,3 +1932,97 @@ export class TestRemoteAgentService implements IRemoteAgentService { async flushTelemetry(): Promise { } async getRoundTripTime(): Promise { return undefined; } } + +export class TestWorkbenchExtensionEnablementService implements IWorkbenchExtensionEnablementService { + _serviceBrand: undefined; + onEnablementChanged = Event.None; + getEnablementState(extension: IExtension): EnablementState { return EnablementState.EnabledGlobally; } + getEnablementStates(extensions: IExtension[], workspaceTypeOverrides?: { trusted?: boolean | undefined } | undefined): EnablementState[] { return []; } + getDependenciesEnablementStates(extension: IExtension): [IExtension, EnablementState][] { return []; } + canChangeEnablement(extension: IExtension): boolean { return true; } + canChangeWorkspaceEnablement(extension: IExtension): boolean { return true; } + isEnabled(extension: IExtension): boolean { return true; } + isEnabledEnablementState(enablementState: EnablementState): boolean { return true; } + isDisabledGlobally(extension: IExtension): boolean { return false; } + async setEnablement(extensions: IExtension[], state: EnablementState): Promise { return []; } + async updateExtensionsEnablementsWhenWorkspaceTrustChanges(): Promise { } +} + +export class TestWorkbenchExtensionManagementService implements IWorkbenchExtensionManagementService { + _serviceBrand: undefined; + onInstallExtension = Event.None; + onDidInstallExtensions = Event.None; + onUninstallExtension = Event.None; + onDidUninstallExtension = Event.None; + onDidChangeProfileExtensions = Event.None; + installVSIX(location: URI, manifest: Readonly, installOptions?: InstallVSIXOptions | undefined): Promise { + throw new Error('Method not implemented.'); + } + installWebExtension(location: URI): Promise { + throw new Error('Method not implemented.'); + } + installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions | undefined): Promise { + throw new Error('Method not implemented.'); + } + async updateFromGallery(gallery: IGalleryExtension, extension: ILocalExtension, installOptions?: InstallOptions | undefined): Promise { return extension; } + getExtensionManagementServerToInstall(manifest: Readonly): IExtensionManagementServer | null { + throw new Error('Method not implemented.'); + } + zip(extension: ILocalExtension): Promise { + throw new Error('Method not implemented.'); + } + unzip(zipLocation: URI): Promise { + throw new Error('Method not implemented.'); + } + getManifest(vsix: URI): Promise> { + throw new Error('Method not implemented.'); + } + install(vsix: URI, options?: InstallVSIXOptions | undefined): Promise { + throw new Error('Method not implemented.'); + } + async canInstall(extension: IGalleryExtension): Promise { return false; } + installFromGallery(extension: IGalleryExtension, options?: InstallOptions | undefined): Promise { + throw new Error('Method not implemented.'); + } + uninstall(extension: ILocalExtension, options?: UninstallOptions | undefined): Promise { + throw new Error('Method not implemented.'); + } + async reinstallFromGallery(extension: ILocalExtension): Promise { + } + async getInstalled(type?: ExtensionType | undefined): Promise { return []; } + getExtensionsControlManifest(): Promise { + throw new Error('Method not implemented.'); + } + getMetadata(extension: ILocalExtension): Promise | undefined> { + throw new Error('Method not implemented.'); + } + async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { return local; } + async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { return local; } + registerParticipant(pariticipant: IExtensionManagementParticipant): void { } + async getTargetPlatform(): Promise { return TargetPlatform.UNDEFINED; } +} + +export class TestWebExtensionsScannerService implements IWebExtensionsScannerService { + _serviceBrand: undefined; + async scanSystemExtensions(): Promise { return []; } + async scanUserExtensions(options?: ScanOptions | undefined): Promise { return []; } + async scanExtensionsUnderDevelopment(): Promise { return []; } + scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise { + throw new Error('Method not implemented.'); + } + addExtension(location: URI, metadata?: Partial | undefined): Promise { + throw new Error('Method not implemented.'); + } + addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Partial | undefined): Promise { + throw new Error('Method not implemented.'); + } + removeExtension(identifier: IExtensionIdentifier, version?: string | undefined): Promise { + throw new Error('Method not implemented.'); + } + scanMetadata(extensionLocation: URI): Promise | undefined> { + throw new Error('Method not implemented.'); + } + scanExtensionManifest(extensionLocation: URI): Promise | null> { + throw new Error('Method not implemented.'); + } +} diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index ded96d4e010..1880f3377d6 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -290,7 +290,7 @@ export function workbenchInstantiationService(disposables = new DisposableStore( instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService); instantiationService.stub(INativeWorkbenchEnvironmentService, TestEnvironmentService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, new FileService(new NullLogService()), new NullLogService())); - instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); + instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); return instantiationService; } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 871b0fd62d3..14407f52692 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -83,7 +83,7 @@ import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRe import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; -import 'vs/workbench/services/userDataProfile/common/userDataProfileWorkbenchService'; +import 'vs/workbench/services/userDataProfile/common/userDataProfileImportExportService'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -329,7 +329,7 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; import 'vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution'; // Continue Edit Session -import 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution'; +import 'vs/workbench/contrib/editSessions/browser/editSessions.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/browser/codeActions.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 6b7b44d4155..8831bf9469b 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -4,51 +4,157 @@ *--------------------------------------------------------------------------------------------*/ -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### + +//#region --- workbench common + +import 'vs/workbench/workbench.common.main'; + +//#endregion -//#region --- workbench common & sandbox +//#region --- workbench (desktop main) -import 'vs/workbench/workbench.sandbox.main'; +import 'vs/workbench/electron-sandbox/desktop.main'; +import 'vs/workbench/electron-sandbox/desktop.contribution'; + +//#endregion + + +//#region --- workbench parts + +import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution'; //#endregion //#region --- workbench services +import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; +import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService'; +import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService'; +import 'vs/workbench/services/textMate/browser/nativeTextMateService'; +import 'vs/workbench/services/menubar/electron-sandbox/menubarService'; +import 'vs/workbench/services/issue/electron-sandbox/issueService'; +import 'vs/workbench/services/update/electron-sandbox/updateService'; +import 'vs/workbench/services/url/electron-sandbox/urlService'; +import 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService'; +import 'vs/workbench/services/title/electron-sandbox/titleService'; +import 'vs/workbench/services/host/electron-sandbox/nativeHostService'; +import 'vs/workbench/services/request/electron-sandbox/requestService'; +import 'vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService'; +import 'vs/workbench/services/clipboard/electron-sandbox/clipboardService'; +import 'vs/workbench/services/contextmenu/electron-sandbox/contextmenuService'; +import 'vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService'; +import 'vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService'; +import 'vs/workbench/services/accessibility/electron-sandbox/accessibilityService'; +import 'vs/workbench/services/path/electron-sandbox/pathService'; +import 'vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService'; +import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService'; +import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService'; +import 'vs/workbench/services/credentials/electron-sandbox/credentialsService'; +import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; +import 'vs/workbench/services/localization/electron-sandbox/languagePackService'; +import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; +import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; +import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService'; +import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; +import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; +import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService'; +import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService'; +import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService'; +import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService'; +import 'vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService'; +import 'vs/workbench/services/timer/electron-sandbox/timerService'; +import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; +import 'vs/workbench/services/integrity/electron-sandbox/integrityService'; +import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; +import 'vs/workbench/services/checksum/electron-sandbox/checksumService'; +import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService'; +import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService'; +import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; +import 'vs/platform/profiling/electron-sandbox/profilingService'; +import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; +import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; +import 'vs/workbench/services/search/electron-sandbox/searchService'; +import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; +import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; +import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService'; -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -import 'vs/workbench/services/extensions/electron-browser/nativeExtensionService'; - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -// NOTE: Please do NOT register services here. Use `registerSingleton()` -// from `workbench.common.main.ts` if the service is shared between -// desktop and web or `workbench.sandbox.main.ts` if the service -// is desktop only. -// -// The `node` & `electron-browser` layer is deprecated for workbench! -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; +registerSingleton(IUserDataInitializationService, UserDataInitializationService); + +//#endregion + + +//#region --- workbench contributions + +// Logs +import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; + +// Localizations +import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; + +// Explorer +import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; +import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution'; + +// CodeEditor Contributions +import 'vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution'; + +// Debug +import 'vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService'; + +// Extensions Management +import 'vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution'; + +// Issues +import 'vs/workbench/contrib/issue/electron-sandbox/issue.contribution'; + +// Remote +import 'vs/workbench/contrib/remote/electron-sandbox/remote.contribution'; + +// Configuration Exporter +import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper.contribution'; + +// Terminal +import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution'; + +// Themes Support +import 'vs/workbench/contrib/themes/browser/themes.test.contribution'; + +// User Data Sync +import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution'; + +// Output +import 'vs/workbench/contrib/output/electron-sandbox/outputChannelModelService'; + +// Tags +import 'vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService'; +import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution'; + +// Performance +import 'vs/workbench/contrib/performance/electron-sandbox/performance.contribution'; + +// Tasks +import 'vs/workbench/contrib/tasks/electron-sandbox/taskService'; + +// External terminal +import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution'; + +// Webview +import 'vs/workbench/contrib/webview/electron-sandbox/webview.contribution'; + +// Splash +import 'vs/workbench/contrib/splash/electron-sandbox/splash.contribution'; + +// Local History +import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution'; //#endregion diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts deleted file mode 100644 index 894e6785212..00000000000 --- a/src/vs/workbench/workbench.desktop.sandbox.main.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -// ####################################################################### -// ### ### -// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### -// ### ### -// ####################################################################### - - -//#region --- workbench common & sandbox - -import 'vs/workbench/workbench.sandbox.main'; - -//#endregion - - -//#region --- workbench (desktop main) - -import 'vs/workbench/electron-sandbox/desktop.main'; - -//#endregion - - -//#region --- workbench services - -import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService'; - -//#endregion diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts deleted file mode 100644 index dd9ddd715b1..00000000000 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - -// ####################################################################### -// ### ### -// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### -// ### ### -// ####################################################################### - -//#region --- workbench common - -import 'vs/workbench/workbench.common.main'; - -//#endregion - - -//#region --- workbench (desktop main) - -import 'vs/workbench/electron-sandbox/desktop.main'; -import 'vs/workbench/electron-sandbox/desktop.contribution'; - -//#endregion - - -//#region --- workbench parts - -import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution'; - -//#endregion - - -//#region --- workbench services - -import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; -import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService'; -import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService'; -import 'vs/workbench/services/textMate/browser/nativeTextMateService'; -import 'vs/workbench/services/menubar/electron-sandbox/menubarService'; -import 'vs/workbench/services/issue/electron-sandbox/issueService'; -import 'vs/workbench/services/update/electron-sandbox/updateService'; -import 'vs/workbench/services/url/electron-sandbox/urlService'; -import 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService'; -import 'vs/workbench/services/title/electron-sandbox/titleService'; -import 'vs/workbench/services/host/electron-sandbox/nativeHostService'; -import 'vs/workbench/services/request/electron-sandbox/requestService'; -import 'vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService'; -import 'vs/workbench/services/clipboard/electron-sandbox/clipboardService'; -import 'vs/workbench/services/contextmenu/electron-sandbox/contextmenuService'; -import 'vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService'; -import 'vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService'; -import 'vs/workbench/services/accessibility/electron-sandbox/accessibilityService'; -import 'vs/workbench/services/path/electron-sandbox/pathService'; -import 'vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService'; -import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService'; -import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService'; -import 'vs/workbench/services/credentials/electron-sandbox/credentialsService'; -import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; -import 'vs/workbench/services/localization/electron-sandbox/languagePackService'; -import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; -import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; -import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService'; -import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService'; -import 'vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService'; -import 'vs/workbench/services/timer/electron-sandbox/timerService'; -import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; -import 'vs/workbench/services/integrity/electron-sandbox/integrityService'; -import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; -import 'vs/workbench/services/checksum/electron-sandbox/checksumService'; -import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService'; -import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService'; -import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; -import 'vs/platform/profiling/electron-sandbox/profilingService'; -import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; -import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; -import 'vs/workbench/services/search/electron-sandbox/searchService'; -import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; -import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; - -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; - -registerSingleton(IUserDataInitializationService, UserDataInitializationService); - -//#endregion - - -//#region --- workbench contributions - -// Logs -import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; - -// Localizations -import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; - -// Explorer -import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; -import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution'; - -// CodeEditor Contributions -import 'vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution'; - -// Debug -import 'vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService'; - -// Extensions Management -import 'vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution'; - -// Issues -import 'vs/workbench/contrib/issue/electron-sandbox/issue.contribution'; - -// Remote -import 'vs/workbench/contrib/remote/electron-sandbox/remote.contribution'; - -// Configuration Exporter -import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper.contribution'; - -// Terminal -import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution'; - -// Themes Support -import 'vs/workbench/contrib/themes/browser/themes.test.contribution'; - -// User Data Sync -import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution'; - -// Output -import 'vs/workbench/contrib/output/electron-sandbox/outputChannelModelService'; - -// Tags -import 'vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService'; -import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution'; - -// Performance -import 'vs/workbench/contrib/performance/electron-sandbox/performance.contribution'; - -// Tasks -import 'vs/workbench/contrib/tasks/electron-sandbox/taskService'; - -// External terminal -import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution'; - -// Webview -import 'vs/workbench/contrib/webview/electron-sandbox/webview.contribution'; - -// Splash -import 'vs/workbench/contrib/splash/electron-sandbox/splash.contribution'; - -// Local History -import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution'; - -//#endregion diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 7e0124da203..31027c91115 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -10166,6 +10166,8 @@ declare module 'vscode' { * This includes drops from within the same tree. * The mime type of a tree is recommended to be of the format `application/vnd.code.tree.`. * + * Use the special `files` mime type to support all types of dropped files {@link DataTransferFile files}, regardless of the file's actual mime type. + * * To learn the mime type of a dragged item: * 1. Set up your `DragAndDropController` * 2. Use the Developer: Set Log Level... command to set the level to "Debug" diff --git a/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts b/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts new file mode 100644 index 00000000000..a38d03f4fde --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `file/share`-submenu contribution point diff --git a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts index 95963aeaea5..b1e9211ceb2 100644 --- a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts @@ -47,7 +47,7 @@ declare module 'vscode' { /** * Content providers should always use {@link FileSystemProvider file system providers} to - * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. + * resolve the raw content for `uri` as the resource is not necessarily a file on disk. */ openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; diff --git a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts b/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts index 43305c7b56e..46c485216c2 100644 --- a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts +++ b/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts @@ -7,10 +7,9 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/145374 - export interface TextEdit { + interface WorkspaceEdit { - // will be merged with newText - // will NOT be supported everywhere, only: `workspace.applyEdit` - newText2?: string | SnippetString; + // todo@API have a SnippetTextEdit and allow to set that? + replace(uri: Uri, range: Range, newText: string | SnippetString, metadata?: WorkspaceEditEntryMetadata): void; } } diff --git a/src/vscode-dts/vscode.proposed.testCoverage.d.ts b/src/vscode-dts/vscode.proposed.testCoverage.d.ts index 3d7092ebb3c..7ed2627673a 100644 --- a/src/vscode-dts/vscode.proposed.testCoverage.d.ts +++ b/src/vscode-dts/vscode.proposed.testCoverage.d.ts @@ -30,7 +30,7 @@ declare module 'vscode' { /** * Give a FileCoverage to fill in more data, namely {@link FileCoverage.detailedCoverage}. - * The editor will only resolve a FileCoverage once, and onyl if detailedCoverage + * The editor will only resolve a FileCoverage once, and only if detailedCoverage * is undefined. * * @param coverage A coverage object obtained from {@link provideFileCoverage} diff --git a/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts index 4fbd0c053ec..5d4b2b50fc2 100644 --- a/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts @@ -142,7 +142,7 @@ declare module 'vscode' { } /** - * Represents the severiry of a TextSearchComplete message. + * Represents the severity of a TextSearchComplete message. */ export enum TextSearchCompleteMessageType { Information = 1, diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index a6c86ba20d1..4a5b62d8d7a 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -12,8 +12,7 @@ export function setup(logger: Logger) { // Shared before/after handling installAllHandlers(logger); - // skipped until translations are available https://github.com/microsoft/vscode/issues/150324 - it.skip('starts with "DE" locale and verifies title and viewlets text is in German', async function () { + it('starts with "DE" locale and verifies title and viewlets text is in German', async function () { const app = this.app as Application; await app.workbench.extensions.openExtensionsViewlet(); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index ecc11bc9a50..e73fb8b7865 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -702,9 +702,9 @@ shebang-regex@^1.0.0: integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= shell-quote@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== spdx-correct@^3.0.0: version "3.1.1" diff --git a/yarn.lock b/yarn.lock index cfa2dc717d5..9c823e24f88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -850,35 +850,6 @@ "@microsoft/applicationinsights-shims" "^2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-analytics-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.8.4.tgz#3b32d8a2122be5d5993c74ef3217ebbf4876ea69" - integrity sha512-n/FPs8SS6rB8h+u157fiRh0TwUWKctxGNvr4M+LKeSdgDvf9c759gUeMR7r8xF6kBBfgkbmyaVORjsA1WJsU4g== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-channel-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.4.tgz#6de7210d87e61c72d3a9a06cbaeae14e1b543484" - integrity sha512-aml49Jya8LxX4tvyBbIvcxSo7UGI0k3HeiJQRFLeO+QlA+Ocsl10PqphU/OYJ4hh/P5/2QhEAq5bBM/b9/PNrg== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-common@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.4.tgz#45b422cf1804df06d5abb2ceda5ed65268a92135" - integrity sha512-uDvd4zxNGNYFE0TF4h7tAg+eMIPatyd1QdkP8fA4UYwshF4/+UwS1wegjXLEWQRRH87+UAyvx4IKQjobzzEX0A== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-core-js@2.8.3": version "2.8.3" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.3.tgz#d07abd6e8bfec7d101518494ad4bd62516df5c51" @@ -887,53 +858,11 @@ "@microsoft/applicationinsights-shims" "2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== - dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-dependencies-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.8.4.tgz#b9afbb81fb44aeb3033ecff1d2e4d33d71c1d41c" - integrity sha512-dr11EBFBR+vmtTipubZv9KSWRXLk6XdutkEgilgzXdSFun0dqR+ZSHEmMWyqE8ZsJtW+1HzdKuGtODSQY6uHyw== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-properties-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.8.4.tgz#c52a6ce03b8f99b2110a097b4ef30686dfb433f8" - integrity sha512-UI0afK5e8yUJ1qIdy+7FA/G9TB+st0++trx4bUMa+Hb6gJggdQPq94lBFJL0yzo4QsgQwozVwkInXy4534tTYQ== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== -"@microsoft/applicationinsights-web@^2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.8.4.tgz#6b385d385790b9574dad0754b860e656cde470b7" - integrity sha512-3CtZiM6e5Q0AA+1NE4k8A0+Y0FE1jsK4u0sb4AkvV7b4cwb86I9l7F7fQPU+V/ltkni0g2WtDrMNU93RuxSmNw== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.8.4" - "@microsoft/applicationinsights-channel-js" "2.8.4" - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-dependencies-js" "2.8.4" - "@microsoft/applicationinsights-properties-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/dynamicproto-js@^1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" @@ -1191,13 +1120,6 @@ resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== -"@types/applicationinsights@0.20.0": - version "0.20.0" - resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" - integrity sha512-dQ3Hb58ERe5YNKFVyvU9BrEvpgKeb6Ht9HkCyBvsOZxhx6yKSwF3e+xml3PJQ3JiVOvf6gM/PmE3MdWDl1L6aA== - dependencies: - applicationinsights "*" - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -2217,26 +2139,6 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" -applicationinsights@*: - version "1.5.0" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.5.0.tgz#074df9e525dcfd592822e7b80723b9284d2716fd" - integrity sha512-D+JyPrDx9RWVNIwukoe03ANKNdyVe/ejExbR7xMvZTm09553TzXenW2oPZmfN9jeguKSDugzIWdbILMPNSRRlg== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - -applicationinsights@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" - integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -2452,21 +2354,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" - -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -3236,15 +3123,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== - dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3477,14 +3355,6 @@ content-type@^1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - convert-source-map@^1.0.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -4147,18 +4017,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== -diagnostic-channel-publishers@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.3.tgz#376b7798f4fa90f37eb4f94d2caca611b0e9c330" - integrity sha512-qIocRYU5TrGUkBlDDxaziAK1+squ8Yf2Ls4HldL3xxb/jzmWO2Enux7CvevNKYmF2kDXZ9HiRqwjPsjk8L+i2Q== - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc= - dependencies: - semver "^5.3.0" - diff-sequences@^27.4.0: version "27.4.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" @@ -4357,13 +4215,6 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emitter-listener@^1.0.1, emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -10155,7 +10006,7 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -10292,14 +10143,9 @@ shebang-regex@^3.0.0: integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.6.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== - -shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== sigmund@^1.0.1: version "1.0.1" @@ -10610,11 +10456,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -11448,10 +11289,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.8.0-dev.20220614: - version "4.8.0-dev.20220614" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220614.tgz#5b3f65db9667b054667f891d79cebb0e2993c162" - integrity sha512-pZVLT8Li6ZCYmyv6F+xEJ+nZ947uRE7el5OloggrPZdhjDJpBBBn/CK5YCgnXcmZFgwO6WMbco1GwfsR23CsxA== +typescript@^4.8.0-dev.20220706: + version "4.8.0-dev.20220706" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220706.tgz#5f2c703258f08468eac5d1147a8604954681a6b4" + integrity sha512-kQGVsx25I0KFyzMwrZTm+umjHRDA31SUH4WBtJfCaSmr67CH0vKu8XSaPCl84zb39cAnfHpI4S8qLvkgDaAmqQ== typical@^4.0.0: version "4.0.0" @@ -12256,35 +12097,35 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.9.0-beta.39: - version "0.9.0-beta.39" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.39.tgz#e8376e1485ee7d763c07d1a8f1354114f65b3e3e" - integrity sha512-h45wkecgfqXXoAUqgNytAfSd6g0xNT6rZy/enVaEU0aes7QoL9pxHUKkCry8PP6hs03Slk0VxQ4AGsbSZGvK/w== +xterm-addon-search@0.10.0-beta.1: + version "0.10.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.1.tgz#ee15b954b6f78585cd3a212ec662018263470266" + integrity sha512-rp68SwoYHIQ1SY4MoILNK+0HcN8OR4hzczHOYCFdeKYZFvH/16vgqg0OJT6t6WlL1cq971rLsEDXT1SKcpoJqA== -xterm-addon-serialize@0.7.0-beta.13: - version "0.7.0-beta.13" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.13.tgz#5c859c8657cab7f28405aab1a0715daf54bc7714" - integrity sha512-TYFlm/gds0pOmpzXw7ZWx8Cy48lMaOZZqZgfm5pWU37HPvzfKxXSVdYL1biWpRCH2zCH+3cWmOma8W1pBRr+Eg== +xterm-addon-serialize@0.8.0-beta.1: + version "0.8.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.1.tgz#d1496da20006afa81874a717e3a0f75fc71dc87a" + integrity sha512-CfS0do/GM8e3k0+3O6GNDi4Gbhhkx1ne1nnnkILWQaAmlArLySEL8f0uPR0W72AtlLEFwVF8kABbVTjKc5XUcA== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.41: - version "0.12.0-beta.41" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.41.tgz#17dbca975b6e9b34526ebc57f4de59ee295da1b6" - integrity sha512-wvQxC5diMYEJEMaILfz+4CWB2GgtzjzNQRNDnK7R7Y9wDI+P4idDlQKgyH0nA93sl9R4zgqlBVha//wuq4vfZg== +xterm-addon-webgl@0.13.0-beta.2: + version "0.13.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" + integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== -xterm-headless@4.19.0-beta.60: - version "4.19.0-beta.60" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.60.tgz#ccd09a7c69ba487d3fd87d6a8d159f1a6a10e9b2" - integrity sha512-mJ24BRnEDCASJDTGFZysZxiXkCmed3eMFiWCPvIF48LVrqxt1cImoVKBlxqkdgUkHKjfF2c92S1TTBL5aT1I0w== +xterm-headless@4.20.0-beta.5: + version "4.20.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641" + integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw== -xterm@4.19.0-beta.60: - version "4.19.0-beta.60" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.60.tgz#de89d93153ae3ec17b53f3e5d03b8ea859126081" - integrity sha512-YQjvp4xtSAyNm9+gF4OA5+QzkSWdKMCy4WABETpe7CsrEGx+mJVoXCZ9wgopmvwRXz7DeJvKM5dWz1HPjVLDtA== +xterm@4.20.0-beta.5: + version "4.20.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" + integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== y18n@^3.2.1: version "3.2.2"