diff --git a/.github/workflows/author-verified.yml b/.github/workflows/author-verified.yml index 7061cf3cdd2..f914be2f71b 100644 --- a/.github/workflows/author-verified.yml +++ b/.github/workflows/author-verified.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout Actions if: contains(github.event.issue.labels.*.name, 'author-verification-requested') && contains(github.event.issue.labels.*.name, 'insiders-released') - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/bad-tag.yml b/.github/workflows/bad-tag.yml index ba4e0524ccc..bc964fb0582 100644 --- a/.github/workflows/bad-tag.yml +++ b/.github/workflows/bad-tag.yml @@ -8,7 +8,7 @@ jobs: if: github.event.ref == '1.999.0' steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 012c4247abe..605d73183d4 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -19,7 +19,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment @@ -79,7 +79,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -141,7 +141,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf63d22e313..92f596fb9f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: CHILD_CONCURRENCY: "1" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -101,7 +101,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment @@ -182,7 +182,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -254,7 +254,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/deep-classifier-assign-monitor.yml b/.github/workflows/deep-classifier-assign-monitor.yml index 97e375694e6..cfd9abc374a 100644 --- a/.github/workflows/deep-classifier-assign-monitor.yml +++ b/.github/workflows/deep-classifier-assign-monitor.yml @@ -9,7 +9,7 @@ jobs: if: ${{ contains(github.event.issue.labels.*.name, 'triage-needed') }} steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index 2d90770bd25..5ee7d048945 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/deep-classifier-scraper.yml b/.github/workflows/deep-classifier-scraper.yml index d58c02cad0f..e21061549d9 100644 --- a/.github/workflows/deep-classifier-scraper.yml +++ b/.github/workflows/deep-classifier-scraper.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/deep-classifier-unassign-monitor.yml b/.github/workflows/deep-classifier-unassign-monitor.yml index 6e9a2b13621..d0e14e936c2 100644 --- a/.github/workflows/deep-classifier-unassign-monitor.yml +++ b/.github/workflows/deep-classifier-unassign-monitor.yml @@ -9,7 +9,7 @@ jobs: if: ${{ ! contains(github.event.issue.labels.*.name, 'triage-needed') }} steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/devcontainer-cache.yml b/.github/workflows/devcontainer-cache.yml index eeccbdc958d..4e08944ea53 100644 --- a/.github/workflows/devcontainer-cache.yml +++ b/.github/workflows/devcontainer-cache.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Azure CLI login id: az_login diff --git a/.github/workflows/english-please.yml b/.github/workflows/english-please.yml index 2f24b039125..9e04d6d549c 100644 --- a/.github/workflows/english-please.yml +++ b/.github/workflows/english-please.yml @@ -10,7 +10,7 @@ jobs: if: contains(github.event.issue.labels.*.name, '*english-please') steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/feature-request.yml b/.github/workflows/feature-request.yml index 4e054c030eb..83c0a9705c5 100644 --- a/.github/workflows/feature-request.yml +++ b/.github/workflows/feature-request.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout Actions if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request') - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.github/workflows/latest-release-monitor.yml b/.github/workflows/latest-release-monitor.yml index ac60450aa96..f7392dc24a8 100644 --- a/.github/workflows/latest-release-monitor.yml +++ b/.github/workflows/latest-release-monitor.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.github/workflows/locker.yml b/.github/workflows/locker.yml index 8b515b58bd0..5860349a437 100644 --- a/.github/workflows/locker.yml +++ b/.github/workflows/locker.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index a276604164e..df9138035cb 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -18,7 +18,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/needs-more-info-closer.yml b/.github/workflows/needs-more-info-closer.yml index 65805be0d51..8db8a4246a3 100644 --- a/.github/workflows/needs-more-info-closer.yml +++ b/.github/workflows/needs-more-info-closer.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.github/workflows/on-comment.yml b/.github/workflows/on-comment.yml index 0db46b3f9b6..089aa77c1e7 100644 --- a/.github/workflows/on-comment.yml +++ b/.github/workflows/on-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.github/workflows/on-label.yml b/.github/workflows/on-label.yml index 9771860d437..da80bb54992 100644 --- a/.github/workflows/on-label.yml +++ b/.github/workflows/on-label.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index 8fef95d9e83..361ac11b946 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/release-pipeline-labeler.yml b/.github/workflows/release-pipeline-labeler.yml index dbe6b966c6f..87e188a02ab 100644 --- a/.github/workflows/release-pipeline-labeler.yml +++ b/.github/workflows/release-pipeline-labeler.yml @@ -10,14 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" ref: stable path: ./actions - name: Checkout Repo if: github.event_name != 'issues' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ./repo fetch-depth: 0 diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index 3bfaf8117c8..ab1559d8fa6 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -7,7 +7,7 @@ jobs: runs-on: 'ubuntu-latest' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' - uses: 'actions/setup-node@v3' with: diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml index d3b9284f9ae..117eaf6908a 100644 --- a/.github/workflows/test-plan-item-validator.yml +++ b/.github/workflows/test-plan-item-validator.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout Actions if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "microsoft/vscode-github-triage-actions" path: ./actions diff --git a/.yarnrc b/.yarnrc index fff0be195f2..bada83ea8bd 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "25.8.0" -ms_build_id "23503258" +target "25.8.1" +ms_build_id "23779380" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index 3fb6234cbcf..8b5303f5785 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -34,7 +34,7 @@ steps: displayName: Download openssl prebuilt inputs: command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.10 customRegistry: useFeed customFeed: "Monaco/openssl-prebuilt" workingDir: $(Build.ArtifactStagingDirectory) @@ -42,7 +42,7 @@ steps: - script: | set -e mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.10.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl displayName: Extract openssl prebuilt # inspired by: https://github.com/emk/rust-musl-builder/blob/main/Dockerfile diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml index ae8f0e84652..f090c811a34 100644 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ b/build/azure-pipelines/darwin/cli-build-darwin.yml @@ -23,7 +23,7 @@ steps: displayName: Download openssl prebuilt inputs: command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.10 customRegistry: useFeed customFeed: "Monaco/openssl-prebuilt" workingDir: $(Build.ArtifactStagingDirectory) @@ -31,7 +31,7 @@ steps: - script: | set -e mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.10.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl displayName: Extract openssl prebuilt - template: ../cli/install-rust-posix.yml diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml index b77b78aa1a8..098829eeb5f 100644 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -26,7 +26,7 @@ steps: displayName: Download openssl prebuilt inputs: command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.10 customRegistry: useFeed customFeed: "Monaco/openssl-prebuilt" workingDir: $(Build.ArtifactStagingDirectory) @@ -34,7 +34,7 @@ steps: - script: | set -e mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.10.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl displayName: Extract openssl prebuilt - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index 6eb5ac41f5c..3d2c08dcf5a 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -26,14 +26,14 @@ steps: displayName: Download openssl prebuilt inputs: command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.10 customRegistry: useFeed customFeed: "Monaco/openssl-prebuilt" workingDir: $(Build.ArtifactStagingDirectory) - powershell: | mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.10.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl displayName: Extract openssl prebuilt - template: ../cli/install-rust-win32.yml @@ -54,8 +54,8 @@ steps: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_x64_cli VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static-md/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static-md/include + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/include RUSTFLAGS: "-C target-feature=+crt-static" - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: @@ -66,8 +66,8 @@ steps: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_arm64_cli VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static-md/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static-md/include + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/include RUSTFLAGS: "-C target-feature=+crt-static" - ${{ if eq(parameters.VSCODE_BUILD_WIN32_32BIT, true) }}: @@ -78,6 +78,6 @@ steps: VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_ia32_cli VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static-md/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static-md/include + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static/include RUSTFLAGS: "-C target-feature=+crt-static" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 9c46b2ad8ef..8355854b079 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,27 +1,27 @@ -88cafda8394985e59d3d84cb4a6692ad04d8e32db9ecd6429e748e41526ddad7 *electron-v25.8.0-darwin-arm64-symbols.zip -6e33d3b8041561722ed41777e055a8c15d3f4e61b67367b2618918bcf0cfea76 *electron-v25.8.0-darwin-arm64.zip -438ac9915e062a239fb6d2595323c4783d2c820efc9cbcf3d2c1253d0e057e83 *electron-v25.8.0-darwin-x64-symbols.zip -798907d2a66bc79202c8213c61e7fd147ae2a8c31c485d814950b11d43bbbba8 *electron-v25.8.0-darwin-x64.zip -3243f3764319cff6c942d9f90a86323c36ec05ec51ef01e782c4e9a7194187e1 *electron-v25.8.0-linux-arm64-symbols.zip -f24f858b76bf8a2e18419f62e0f891712b2fa541089123e9caa8d5cd67fc3276 *electron-v25.8.0-linux-arm64.zip -dc3ff0489a0ebeda56d06b31eeae75dd7321a52bb601069c4475c56462b4814a *electron-v25.8.0-linux-armv7l-symbols.zip -3b7a0c3899f828a5cf30043b73992e90231400b90c1afa700a44f892a55e326b *electron-v25.8.0-linux-armv7l.zip -44803b2487406eca8fff9cec405e9e50bd92a911808dfaaa523b9ef52a0e72d8 *electron-v25.8.0-linux-x64-symbols.zip -d54fb2df0ad7318240220aa26327171ed1e891fb296f3c27c58b8b487c4df8eb *electron-v25.8.0-linux-x64.zip -bf7be6c0c8d0df06f0ce22e16c97aea823415d7f5cbf0ffdadf65d75feaf3cd8 *electron-v25.8.0-win32-arm64-pdb.zip -5d91757660b44bf30907f9c2b52225ade4d127d0fe48dc83dec134cc06c949f0 *electron-v25.8.0-win32-arm64-symbols.zip -d1e6f30a8d8c7aed28d08ddf915d79de6b16b3a0a7c84c45fd3cc0d47f2b7f53 *electron-v25.8.0-win32-arm64.zip -e389fef61c14ea0eefad91a9725aa0afd4dbdc982f7b30aba97bd9c2871c2061 *electron-v25.8.0-win32-ia32-pdb.zip -374d6c8897f97fab04e990ecf928e05f643ae33801546bf7d39bf4045b9d8b52 *electron-v25.8.0-win32-ia32-symbols.zip -73fc3382202b70dcaf7928f09a791662de82c701b8f403ed72cc5aa9b1401593 *electron-v25.8.0-win32-ia32.zip -010d248bd2e77585e1fa977e58b016659566de5a91c1e6845c85a7e6e1851bb9 *electron-v25.8.0-win32-x64-pdb.zip -72adb74fd92edff35c177c3c5d96765f230bc7adb8af11b30d5122b9e54c26e1 *electron-v25.8.0-win32-x64-symbols.zip -0051d0f241aedc6cdab4751c60f48758936122796f06c9e3033c7710a531686c *electron-v25.8.0-win32-x64.zip -2956915642c45eb0099228368d0af50e891e4c10014fa4d3d3bcfb135fbb89a7 *ffmpeg-v25.8.0-darwin-arm64.zip -099ee69d44f8ac3802cdd612895f279f7adb043a5b9c9d123479b0f96514a44c *ffmpeg-v25.8.0-darwin-x64.zip -bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.8.0-linux-arm64.zip -9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.8.0-linux-armv7l.zip -edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.8.0-linux-x64.zip -a58e9480dab981ff973749e9d1e08936b2dd63a4b7f9523c030b1833387a4eb5 *ffmpeg-v25.8.0-win32-arm64.zip -6866b23a4d561c0322aeb7690aae646718c54398739946e352bf80d0dd721bfd *ffmpeg-v25.8.0-win32-ia32.zip -7b906df4ad6252881cf1e58619285b624f74d593379fbc6728e238b852d6abad *ffmpeg-v25.8.0-win32-x64.zip +197a62bdc616148537af5c8f1256563a27a733cae0d41b9a42e9e9f2901cc9ae *electron-v25.8.1-darwin-arm64-symbols.zip +985f4beee009ef6dbe7af75009a1b281aff591b989ee544bd4d2c814f9c4428d *electron-v25.8.1-darwin-arm64.zip +56209b302eb30bff3727dbb74467cb2e6e93e5a99d6939d1c3a7c8320fa26d72 *electron-v25.8.1-darwin-x64-symbols.zip +fe61a3221d4c5dc9415dc2cce81010db61bb4a5ab5021d1023f48786dbaf0b28 *electron-v25.8.1-darwin-x64.zip +1b52beba5b1b133cccd253c32648cdfa51659e5db2f20441baafd6a3c8fcb56a *electron-v25.8.1-linux-arm64-symbols.zip +931208419f859f528e19805ae14b5b075213bc40dd20da9e03d8b71d849c147d *electron-v25.8.1-linux-arm64.zip +61929b61541f23b75cca6b1a6764c219499a9799376298cc9061fc1a8716d1e1 *electron-v25.8.1-linux-armv7l-symbols.zip +dd3390de2426a1cea9d3176e404b8fb250844a9b0e48cf01822fa66f3c3c4a48 *electron-v25.8.1-linux-armv7l.zip +1812459d2bd0440e35134ddb3f56e8e173a8791db888756d22080ace244248ba *electron-v25.8.1-linux-x64-symbols.zip +de556aef0a80a8fe7b2b39d74ab0bdecc65d72bba3b7349460d18ef2a22fa323 *electron-v25.8.1-linux-x64.zip +baa1bb8985b09100b1b2b710cea2c3f8e72a7f5d0e3421cbf675c25e732b0d9f *electron-v25.8.1-win32-arm64-pdb.zip +4a22945c2584677a822d8f9eb6cb23e5607b985f85ec7523ab977b73dd6487c2 *electron-v25.8.1-win32-arm64-symbols.zip +567d3188f86176a875fb6562040a3767c638814cf06073e7131495ec681b3957 *electron-v25.8.1-win32-arm64.zip +148589f41cd4175b85138450a0a0e32eef1a519d5cb5f471374e167518a2ed47 *electron-v25.8.1-win32-ia32-pdb.zip +f284ac337a8d5e564081fee6d26c2b2f782feed0bcbe29b7507f749da36b6e5c *electron-v25.8.1-win32-ia32-symbols.zip +6737e56588a2707534979e5dcfbf68399971a5e98d4848fa29fb43b7ae1dc378 *electron-v25.8.1-win32-ia32.zip +23c6ef26f7e1914149def7525b728212340fc6befa6bac30eafec8f8229f9e67 *electron-v25.8.1-win32-x64-pdb.zip +3eb3b87587b136e636d82fa4d28015d8ef6709468b3bb33eddae80d83076a053 *electron-v25.8.1-win32-x64-symbols.zip +963c05266bf83bc77f690b75a76855d7b7bc0be9bac4973292f3e7d506fd2c94 *electron-v25.8.1-win32-x64.zip +ab608119c4bb4ebcefede6afdd911ef8ea007465ab7d2093a091bdb9ff466236 *ffmpeg-v25.8.1-darwin-arm64.zip +5abaf6356bb40fdfcaf88177e13c50447a48fe7fc6c1c3869cf4536148eb60d4 *ffmpeg-v25.8.1-darwin-x64.zip +bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.8.1-linux-arm64.zip +9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.8.1-linux-armv7l.zip +edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.8.1-linux-x64.zip +6b2494dd5bf7f17eec800f41d232506007b36c7cae6f885565e035d3168a0c64 *ffmpeg-v25.8.1-win32-arm64.zip +8b57594cd2065437cf3378e845bae4de3501ebff7f1f1f516fc0aaf09c311c2b *ffmpeg-v25.8.1-win32-ia32.zip +95bb8e167e96bb4f902e6238312e245ed0acfae45eeda471e0f8ad0f0baaac2f *ffmpeg-v25.8.1-win32-x64.zip diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index b7d31b812d8..301b01e45c7 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -734,7 +734,7 @@ "--tab-sizing-current-width", "--tab-sizing-fixed-min-width", "--tab-sizing-fixed-max-width", - "--editor-group-title-height", + "--editor-group-tab-height", "--testMessageDecorationFontFamily", "--testMessageDecorationFontSize", "--title-border-bottom-color", diff --git a/build/win32/code.iss b/build/win32/code.iss index b7336831374..d64516e6a87 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1358,7 +1358,7 @@ begin end; if not Result and not WizardSilent() then begin - MsgBox('Please uninstall the ' + AltArch + '-bit version of {#NameShort} before installing this ' + ThisArch + '-bit version.', mbInformation, MB_OK); + MsgBox('Please uninstall the ' + AltArch + '-bit version of {#NameShort} before installing this ' + ThisArch + '-bit version. Uninstalling will not delete settings.', mbInformation, MB_OK); end; end; diff --git a/cgmanifest.json b/cgmanifest.json index 6b2d2bfff44..5e6c318bf94 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "84d7f7f071ae11637d4a41b95536410293672750" + "commitHash": "dc3ce65dd5fd872db86e5308ae005a898142435a" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "25.8.0" + "version": "25.8.1" }, { "component": { diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index cbc33fcb071..d1bee378ef2 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -220,8 +220,8 @@ pub struct CommandShellArgs { #[clap(long)] pub on_socket: bool, /// Listen on a port instead of stdin/stdout. - #[clap(long)] - pub on_port: bool, + #[clap(long, num_args = 0..=1, default_missing_value = "0")] + pub on_port: Option, /// Require the given token string to be given in the handshake. #[clap(long)] pub require_token: Option, diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index 2181c6339a4..7f200d4341f 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -120,6 +120,7 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), args.port), }; + let builder = Server::try_bind(&addr).map_err(CodeError::CouldNotListenOnInterface)?; let mut listening = format!("Web UI available at http://{}", addr); if let Some(ct) = args.connection_token { @@ -127,7 +128,7 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result Resul Box::new(listener) } - (true, _) => { - let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + (Some(p), _) => { + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), p); + let listener = tokio::net::TcpListener::bind(addr) .await .map_err(|e| wrap(e, "error listening on port"))?; diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index 45e0c9748ef..af0605bb66d 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -278,8 +278,8 @@ fn make_socket_rpc( port_forwarding: Option, requires_auth: AuthRequired, platform: Platform, + http_requests: HttpRequestsMap, ) -> RpcDispatcher { - let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new())); let server_bridges = ServerMultiplexer::new(); let mut rpc = RpcBuilder::new(MsgPackSerializer {}).methods(HandlerContext { did_update: Arc::new(AtomicBool::new(false)), @@ -377,7 +377,10 @@ fn make_socket_rpc( ); rpc.register_sync("httpheaders", |p: HttpHeadersParams, c| { if let Some(req) = c.http_requests.lock().unwrap().get(&p.req_id) { + trace!(c.log, "got {} response for req {}", p.status_code, p.req_id); req.initial_response(p.status_code, p.headers); + } else { + warning!(c.log, "got response for unknown req {}", p.req_id); } Ok(EmptyObject {}) }); @@ -388,6 +391,7 @@ fn make_socket_rpc( req.body(p.segment); } if p.complete { + trace!(c.log, "delegated request {} completed", p.req_id); reqs.remove(&p.req_id); } } @@ -441,6 +445,7 @@ async fn process_socket( port_forwarding, requires_auth, platform, + http_requests.clone(), ); { @@ -497,6 +502,7 @@ async fn process_socket( }), }) .unwrap(); + http_requests.lock().unwrap().insert(id, r); tx_counter += serialized.len(); diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index 316e3672ba6..5665714fed9 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; #[allow(non_camel_case_types)] pub enum ClientRequestMethod<'a> { servermsg(RefServerMessageParams<'a>), + serverclose(ServerClosedParams), serverlog(ServerLog<'a>), makehttpreq(HttpRequestParams<'a>), version(VersionResponse), @@ -89,6 +90,11 @@ pub struct ServerMessageParams { pub body: Vec, } +#[derive(Serialize, Debug)] +pub struct ServerClosedParams { + pub i: u16, +} + #[derive(Serialize, Debug)] pub struct RefServerMessageParams<'a> { pub i: u16, diff --git a/cli/src/tunnels/server_bridge.rs b/cli/src/tunnels/server_bridge.rs index 50dde8e7303..f1a358279af 100644 --- a/cli/src/tunnels/server_bridge.rs +++ b/cli/src/tunnels/server_bridge.rs @@ -32,6 +32,7 @@ impl ServerBridge { match read.read(&mut read_buf).await { Err(_) => return, Ok(0) => { + let _ = target.server_closed().await; return; // EOF } Ok(s) => { diff --git a/cli/src/tunnels/socket_signal.rs b/cli/src/tunnels/socket_signal.rs index 53e6cd51567..9036c6ae3f9 100644 --- a/cli/src/tunnels/socket_signal.rs +++ b/cli/src/tunnels/socket_signal.rs @@ -9,7 +9,7 @@ use tokio::sync::mpsc; use crate::msgpack_rpc::MsgPackCaller; use super::{ - protocol::{ClientRequestMethod, RefServerMessageParams, ToClientRequest}, + protocol::{ClientRequestMethod, RefServerMessageParams, ServerClosedParams, ToClientRequest}, server_multiplexer::ServerMultiplexer, }; @@ -81,25 +81,43 @@ impl ServerMessageSink { } } + pub async fn server_closed(&mut self) -> Result<(), mpsc::error::SendError> { + self.server_message_or_closed(None).await + } + pub async fn server_message( &mut self, body: &[u8], ) -> Result<(), mpsc::error::SendError> { - let id = self.id; + self.server_message_or_closed(Some(body)).await + } + + async fn server_message_or_closed( + &mut self, + body: Option<&[u8]>, + ) -> Result<(), mpsc::error::SendError> { + let i = self.id; let mut tx = self.tx.take().unwrap(); - let body = self.get_server_msg_content(body); - let msg = RefServerMessageParams { i: id, body }; + let msg = body + .map(|b| self.get_server_msg_content(b)) + .map(|body| RefServerMessageParams { i, body }); let r = match &mut tx { ServerMessageDestination::Channel(tx) => { tx.send(SocketSignal::from_message(&ToClientRequest { id: None, - params: ClientRequestMethod::servermsg(msg), + params: match msg { + Some(msg) => ClientRequestMethod::servermsg(msg), + None => ClientRequestMethod::serverclose(ServerClosedParams { i }), + }, })) .await } ServerMessageDestination::Rpc(caller) => { - caller.notify("servermsg", msg); + match msg { + Some(msg) => caller.notify("servermsg", msg), + None => caller.notify("serverclose", ServerClosedParams { i }), + }; Ok(()) } }; diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 520682059e1..b80a187e266 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -23,7 +23,7 @@ "watch": "gulp watch-extension:configuration-editing" }, "dependencies": { - "jsonc-parser": "^2.2.1", + "jsonc-parser": "^3.2.0", "@octokit/rest": "19.0.4", "tunnel": "^0.0.6" }, diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 549c578a9ac..7672e88e7a4 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -3,41 +3,39 @@ "@octokit/auth-token@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.2.tgz#a0fc8de149fd15876e1ac78f6525c1c5ab48435f" - integrity sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q== - dependencies: - "@octokit/types" "^8.0.0" + version "3.0.4" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.4.tgz#70e941ba742bdd2b49bdb7393e821dea8520a3db" + integrity sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ== "@octokit/core@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" - integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== + version "4.2.4" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.4.tgz#d8769ec2b43ff37cc3ea89ec4681a20ba58ef907" + integrity sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ== dependencies: "@octokit/auth-token" "^3.0.0" "@octokit/graphql" "^5.0.0" "@octokit/request" "^6.0.0" "@octokit/request-error" "^3.0.0" - "@octokit/types" "^8.0.0" + "@octokit/types" "^9.0.0" before-after-hook "^2.2.0" universal-user-agent "^6.0.0" "@octokit/endpoint@^7.0.0": - version "7.0.3" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.3.tgz#0b96035673a9e3bedf8bab8f7335de424a2147ed" - integrity sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw== + version "7.0.6" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.6.tgz#791f65d3937555141fb6c08f91d618a7d645f1e2" + integrity sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg== dependencies: - "@octokit/types" "^8.0.0" + "@octokit/types" "^9.0.0" is-plain-object "^5.0.0" universal-user-agent "^6.0.0" "@octokit/graphql@^5.0.0": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.4.tgz#519dd5c05123868276f3ae4e50ad565ed7dff8c8" - integrity sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.6.tgz#9eac411ac4353ccc5d3fca7d76736e6888c5d248" + integrity sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw== dependencies: "@octokit/request" "^6.0.0" - "@octokit/types" "^8.0.0" + "@octokit/types" "^9.0.0" universal-user-agent "^6.0.0" "@octokit/openapi-types@^13.11.0": @@ -50,6 +48,11 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== +"@octokit/openapi-types@^18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69" + integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw== + "@octokit/plugin-paginate-rest@^4.0.0": version "4.3.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.3.1.tgz#553e653ee0318605acd23bf3a799c8bfafdedae3" @@ -63,30 +66,30 @@ integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@^6.0.0": - version "6.7.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" - integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== + version "6.8.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.8.1.tgz#97391fda88949eb15f68dc291957ccbe1d3e8ad1" + integrity sha512-QrlaTm8Lyc/TbU7BL/8bO49vp+RZ6W3McxxmmQTgYxf2sWkO8ZKuj4dLhPNJD6VCUW1hetCmeIM0m6FTVpDiEg== dependencies: - "@octokit/types" "^8.0.0" + "@octokit/types" "^8.1.1" deprecation "^2.3.1" "@octokit/request-error@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.2.tgz#f74c0f163d19463b87528efe877216c41d6deb0a" - integrity sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.3.tgz#ef3dd08b8e964e53e55d471acfe00baa892b9c69" + integrity sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ== dependencies: - "@octokit/types" "^8.0.0" + "@octokit/types" "^9.0.0" deprecation "^2.0.0" once "^1.4.0" "@octokit/request@^6.0.0": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.2.tgz#a2ba5ac22bddd5dcb3f539b618faa05115c5a255" - integrity sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw== + version "6.2.8" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.8.tgz#aaf480b32ab2b210e9dadd8271d187c93171d8eb" + integrity sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw== dependencies: "@octokit/endpoint" "^7.0.0" "@octokit/request-error" "^3.0.0" - "@octokit/types" "^8.0.0" + "@octokit/types" "^9.0.0" is-plain-object "^5.0.0" node-fetch "^2.6.7" universal-user-agent "^6.0.0" @@ -108,13 +111,20 @@ dependencies: "@octokit/openapi-types" "^13.11.0" -"@octokit/types@^8.0.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.1.1.tgz#92e304e0f00d563667dfdbe0ae6b52e70d5149bb" - integrity sha512-7tjk+6DyhYAmei8FOEwPfGKc0VE1x56CKPJ+eE44zhDbOyMT+9yan8apfQFxo8oEFsy+0O7PiBtH8w0Yo0Y9Kw== +"@octokit/types@^8.1.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.2.1.tgz#a6de091ae68b5541f8d4fcf9a12e32836d4648aa" + integrity sha512-8oWMUji8be66q2B9PmEIUyQm00VPDPun07umUWSaCwxmeaquFBro4Hcc3ruVoDo3zkQyZBlRvhIMEYS3pBhanw== dependencies: "@octokit/openapi-types" "^14.0.0" +"@octokit/types@^9.0.0": + version "9.3.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-9.3.2.tgz#3f5f89903b69f6a2d196d78ec35f888c0013cac5" + integrity sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA== + dependencies: + "@octokit/openapi-types" "^18.0.0" + "@types/node@18.x": version "18.15.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" @@ -135,15 +145,15 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -jsonc-parser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== node-fetch@^2.6.7: - version "2.6.8" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" - integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 962188ea971..dd44d10fb75 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "772323937fedd65c6dc1c8ce6ea41d97415ed7d1" + "commitHash": "525e628edad54c0f7aa15b015310df240803ea66" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index c08bea9ce01..908da2f69cc 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/772323937fedd65c6dc1c8ce6ea41d97415ed7d1", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/525e628edad54c0f7aa15b015310df240803ea66", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -210,9 +210,6 @@ { "include": "#else-part" }, - { - "include": "#switch-statement" - }, { "include": "#goto-statement" }, @@ -238,10 +235,10 @@ "include": "#checked-unchecked-statement" }, { - "include": "#lock-statement" + "include": "#context-control-statement" }, { - "include": "#using-statement" + "include": "#context-control-paren-statement" }, { "include": "#labeled-statement" @@ -278,13 +275,10 @@ "include": "#comment" }, { - "include": "#checked-unchecked-expression" + "include": "#expression-operator-expression" }, { - "include": "#typeof-or-default-expression" - }, - { - "include": "#nameof-expression" + "include": "#type-operator-expression" }, { "include": "#default-literal-expression" @@ -305,14 +299,20 @@ "include": "#type-builtin" }, { - "include": "#this-or-base-expression" + "include": "#language-variable" }, { - "include": "#switch-expression" + "include": "#switch-statement-or-expression" + }, + { + "include": "#with-expression" }, { "include": "#conditional-operator" }, + { + "include": "#assignment-expression" + }, { "include": "#expression-operators" }, @@ -370,36 +370,39 @@ ] }, "extern-alias-directive": { - "begin": "\\s*(extern)\\b\\s*(alias)\\b\\s*(@?[_[:alpha:]][_[:alnum:]]*)", + "begin": "\\b(extern)\\s+(alias)\\b", "beginCaptures": { "1": { - "name": "keyword.other.extern.cs" + "name": "keyword.other.directive.extern.cs" }, "2": { - "name": "keyword.other.alias.cs" - }, - "3": { - "name": "variable.other.alias.cs" + "name": "keyword.other.directive.alias.cs" } }, - "end": "(?=;)" + "end": "(?=;)", + "patterns": [ + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "variable.other.alias.cs" + } + ] }, "using-directive": { "patterns": [ { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(static)\\b\\s+(\\b(unsafe)\\b\\s+)?", + "begin": "\\b(?:(global)\\s+)?(using)\\s+(static)\\b\\s*(?:(unsafe)\\b\\s*)?", "beginCaptures": { + "1": { + "name": "keyword.other.directive.global.cs" + }, "2": { - "name": "keyword.other.global.cs" + "name": "keyword.other.directive.using.cs" }, "3": { - "name": "keyword.other.using.cs" + "name": "keyword.other.directive.static.cs" }, "4": { - "name": "keyword.other.static.cs" - }, - "6": { - "name": "storage.modifier.cs" + "name": "storage.modifier.unsafe.cs" } }, "end": "(?=;)", @@ -410,19 +413,22 @@ ] }, { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(\\b(unsafe)\\b\\s+)?(?=(@?[_[:alpha:]][_[:alnum:]]*)\\s*=)", + "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*(?:(unsafe)\\b\\s*)?(@?[_[:alpha:]][_[:alnum:]]*)\\s*(=)", "beginCaptures": { + "1": { + "name": "keyword.other.directive.global.cs" + }, "2": { - "name": "keyword.other.global.cs" + "name": "keyword.other.directive.using.cs" }, "3": { - "name": "keyword.other.using.cs" + "name": "storage.modifier.unsafe.cs" + }, + "4": { + "name": "entity.name.type.alias.cs" }, "5": { - "name": "storage.modifier.cs" - }, - "6": { - "name": "entity.name.type.alias.cs" + "name": "keyword.operator.assignment.cs" } }, "end": "(?=;)", @@ -432,20 +438,17 @@ }, { "include": "#type" - }, - { - "include": "#operator-assignment" } ] }, { - "begin": "(\\b(global)\\b\\s+)?\\b(using)\\s*(?!\\(|\\s|var)", + "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*+(?!\\(|var\\b)", "beginCaptures": { - "2": { - "name": "keyword.other.global.cs" + "1": { + "name": "keyword.other.directive.global.cs" }, - "3": { - "name": "keyword.other.using.cs" + "2": { + "name": "keyword.other.directive.using.cs" } }, "end": "(?=;)", @@ -455,7 +458,10 @@ }, { "name": "entity.name.type.namespace.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" + "match": "\\@?[_[:alpha:]][_[:alnum:]]*" + }, + { + "include": "#punctuation-accessor" }, { "include": "#operator-assignment" @@ -551,7 +557,7 @@ "begin": "\\b(namespace)\\s+", "beginCaptures": { "1": { - "name": "keyword.other.namespace.cs" + "name": "storage.type.namespace.cs" } }, "end": "(?<=\\})|(?=;)", @@ -594,7 +600,7 @@ ] }, "storage-modifier": { - "name": "storage.modifier.cs", + "name": "storage.modifier.$1.cs", "match": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.delegate.cs" + "name": "storage.type.delegate.cs" }, "2": { "patterns": [ @@ -712,7 +718,7 @@ "match": "(enum)\\s+(@?[_[:alpha:]][_[:alnum:]]*)", "captures": { "1": { - "name": "keyword.other.enum.cs" + "name": "storage.type.enum.cs" }, "2": { "name": "entity.name.type.enum.cs" @@ -796,7 +802,7 @@ "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "keyword.other.interface.cs" + "name": "storage.type.interface.cs" }, "2": { "name": "entity.name.type.interface.cs" @@ -853,7 +859,7 @@ "begin": "(?x)\n(record)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "keyword.other.record.cs" + "name": "storage.type.record.cs" }, "2": { "name": "entity.name.type.class.cs" @@ -913,10 +919,10 @@ "begin": "(?x)\n(\\b(record)\\b\\s+)?\n(struct)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "2": { - "name": "keyword.other.record.cs" + "name": "storage.type.record.cs" }, "3": { - "name": "keyword.other.struct.cs" + "name": "storage.type.struct.cs" }, "4": { "name": "entity.name.type.struct.cs" @@ -984,19 +990,11 @@ "patterns": [ { "match": "\\b(in|out)\\b", - "captures": { - "1": { - "name": "storage.modifier.cs" - } - } + "name": "storage.modifier.$1.cs" }, { "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\b", - "captures": { - "1": { - "name": "entity.name.type.type-parameter.cs" - } - } + "name": "entity.name.type.type-parameter.cs" }, { "include": "#comment" @@ -1033,7 +1031,7 @@ "begin": "(where)\\s+(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)", "beginCaptures": { "1": { - "name": "keyword.other.where.cs" + "name": "storage.modifier.where.cs" }, "2": { "name": "entity.name.type.type-parameter.cs" @@ -1045,18 +1043,18 @@ "end": "(?=\\{|where|;|=>)", "patterns": [ { - "name": "keyword.other.class.cs", + "name": "storage.type.class.cs", "match": "\\bclass\\b" }, { - "name": "keyword.other.struct.cs", + "name": "storage.type.struct.cs", "match": "\\bstruct\\b" }, { "match": "(new)\\s*(\\()\\s*(\\))", "captures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "name": "punctuation.parenthesis.open.cs" @@ -1112,7 +1110,7 @@ ] }, "property-declaration": { - "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", + "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|//|/\\*|$)", "beginCaptures": { "1": { "patterns": [ @@ -1144,7 +1142,7 @@ "include": "#property-accessors" }, { - "include": "#expression-body" + "include": "#accessor-getter-expression" }, { "include": "#variable-initializer" @@ -1175,7 +1173,7 @@ ] }, "8": { - "name": "keyword.other.this.cs" + "name": "variable.language.this.cs" } }, "end": "(?<=\\})|(?=;)", @@ -1190,7 +1188,7 @@ "include": "#property-accessors" }, { - "include": "#expression-body" + "include": "#accessor-getter-expression" }, { "include": "#variable-initializer" @@ -1198,10 +1196,10 @@ ] }, "event-declaration": { - "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", + "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s* # first event name\n(?=\\{|;|,|=|//|/\\*|$)", "beginCaptures": { "1": { - "name": "keyword.other.event.cs" + "name": "storage.type.event.cs" }, "2": { "patterns": [ @@ -1221,15 +1219,7 @@ ] }, "9": { - "patterns": [ - { - "name": "entity.name.variable.event.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - } - ] + "name": "entity.name.variable.event.cs" } }, "end": "(?<=\\})|(?=;)", @@ -1240,8 +1230,29 @@ { "include": "#event-accessors" }, + { + "name": "entity.name.variable.event.cs", + "match": "@?[_[:alpha:]][_[:alnum:]]*" + }, { "include": "#punctuation-comma" + }, + { + "begin": "=", + "beginCaptures": { + "0": { + "name": "keyword.operator.assignment.cs" + } + }, + "end": "(?<=,)|(?=;)", + "patterns": [ + { + "include": "#expression" + }, + { + "include": "#punctuation-comma" + } + ] } ] }, @@ -1259,22 +1270,6 @@ } }, "patterns": [ - { - "name": "storage.modifier.cs", - "match": "\\b(private|protected|internal)\\b" - }, - { - "name": "keyword.other.get.cs", - "match": "\\b(get)\\b" - }, - { - "name": "keyword.other.set.cs", - "match": "\\b(set)\\b" - }, - { - "name": "keyword.other.init.cs", - "match": "\\b(init)\\b" - }, { "include": "#comment" }, @@ -1282,13 +1277,36 @@ "include": "#attribute-section" }, { - "include": "#expression-body" + "name": "storage.modifier.$1.cs", + "match": "\\b(private|protected|internal)\\b" }, { - "include": "#block" + "begin": "\\b(get)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-getter" + } + ] }, { - "include": "#punctuation-semicolon" + "begin": "\\b(set|init)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-setter" + } + ] } ] }, @@ -1306,14 +1324,6 @@ } }, "patterns": [ - { - "name": "keyword.other.add.cs", - "match": "\\b(add)\\b" - }, - { - "name": "keyword.other.remove.cs", - "match": "\\b(remove)\\b" - }, { "include": "#comment" }, @@ -1321,10 +1331,108 @@ "include": "#attribute-section" }, { - "include": "#expression-body" + "begin": "\\b(add|remove)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", + "beginCaptures": { + "1": { + "name": "storage.type.accessor.$1.cs" + } + }, + "end": "(?<=\\}|;)|(?=\\})", + "patterns": [ + { + "include": "#accessor-setter" + } + ] + } + ] + }, + "accessor-getter": { + "patterns": [ + { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "contentName": "meta.accessor.getter.cs", + "patterns": [ + { + "include": "#statement" + } + ] }, { - "include": "#block" + "include": "#accessor-getter-expression" + }, + { + "include": "#punctuation-semicolon" + } + ] + }, + "accessor-getter-expression": { + "begin": "=>", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=;|\\})", + "contentName": "meta.accessor.getter.cs", + "patterns": [ + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] + }, + "accessor-setter": { + "patterns": [ + { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.curlybrace.open.cs" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.curlybrace.close.cs" + } + }, + "contentName": "meta.accessor.setter.cs", + "patterns": [ + { + "include": "#statement" + } + ] + }, + { + "begin": "=>", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=;|\\})", + "contentName": "meta.accessor.setter.cs", + "patterns": [ + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] }, { "include": "#punctuation-semicolon" @@ -1425,13 +1533,10 @@ ] }, "constructor-initializer": { - "begin": "\\b(?:(base)|(this))\\b\\s*(?=\\()", + "begin": "\\b(base|this)\\b\\s*(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.base.cs" - }, - "2": { - "name": "keyword.other.this.cs" + "name": "variable.language.$1.cs" } }, "end": "(?<=\\))", @@ -1468,7 +1573,7 @@ ] }, "operator-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", + "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n\\b(?operator)\\b\\s*\n(?[+\\-*/%&|\\^!=~<>]+|true|false)\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1478,7 +1583,7 @@ ] }, "6": { - "name": "keyword.other.operator-decl.cs" + "name": "storage.type.operator.cs" }, "7": { "name": "entity.name.function.cs" @@ -1509,7 +1614,7 @@ "match": "\\b(explicit)\\b", "captures": { "1": { - "name": "keyword.other.explicit.cs" + "name": "storage.modifier.explicit.cs" } } }, @@ -1517,14 +1622,14 @@ "match": "\\b(implicit)\\b", "captures": { "1": { - "name": "keyword.other.implicit.cs" + "name": "storage.modifier.implicit.cs" } } } ] }, "2": { - "name": "keyword.other.operator-decl.cs" + "name": "storage.type.operator.cs" }, "3": { "patterns": [ @@ -1607,19 +1712,19 @@ "begin": "(?", + "beginCaptures": { + "0": { + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=,|})", + "patterns": [ + { + "include": "#expression" + } + ] + }, + { + "begin": "\\b(when)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.conditional.when.cs" + } + }, + "end": "(?==>|,|})", + "patterns": [ + { + "include": "#case-guard" + } + ] + }, + { + "begin": "(?!\\s)", + "end": "(?=\\bwhen\\b|=>|,|})", + "patterns": [ + { + "include": "#pattern" + } + ] + } + ] + }, + "case-guard": { + "patterns": [ + { + "include": "#parenthesized-expression" + }, + { + "include": "#expression" + } + ] + }, + "is-expression": { + "begin": "(?=?", + "beginCaptures": { + "0": { + "name": "keyword.operator.relational.cs" + } + }, + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#expression" + } + ] + }, + "var-pattern": { + "begin": "\\b(var)\\b", + "beginCaptures": { + "1": { + "name": "storage.type.var.cs" + } + }, + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#designation-pattern" + } + ] + }, + "designation-pattern": { + "patterns": [ + { + "include": "#intrusive" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.parenthesis.open.cs" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + }, + "patterns": [ + { + "include": "#punctuation-comma" + }, + { + "include": "#designation-pattern" + } + ] + }, + { + "include": "#simple-designation-pattern" + } + ] + }, + "simple-designation-pattern": { + "patterns": [ + { + "include": "#discard-pattern" + }, + { + "match": "@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.variable.local.cs" + } + ] + }, + "type-pattern": { + "begin": "(?=@?[_[:alpha:]][_[:alnum:]]*)", + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "begin": "\\G", + "end": "(?!\\G[@_[:alpha:]])(?=[\\({@_[:alpha:])}\\],;:=&|^]|(?:\\s|^)\\?|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "include": "#type-subpattern" + } + ] + }, + { + "begin": "(?=[\\({@_[:alpha:]])", + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "include": "#positional-pattern" + }, + { + "include": "#property-pattern" + }, + { + "include": "#simple-designation-pattern" + } + ] + } + ] + }, + "type-subpattern": { + "patterns": [ + { + "include": "#type-builtin" + }, + { + "begin": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(::)", + "beginCaptures": { + "1": { + "name": "entity.name.type.alias.cs" + }, + "2": { + "name": "punctuation.separator.coloncolon.cs" + } + }, + "end": "(?<=[_[:alnum:]])|(?=[.<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" + } + ] + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" + }, + { + "begin": "\\.", + "beginCaptures": { + "0": { + "name": "punctuation.accessor.cs" + } + }, + "end": "(?<=[_[:alnum:]])|(?=[<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "patterns": [ + { + "include": "#intrusive" + }, + { + "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "entity.name.type.cs" + } + ] + }, + { + "include": "#type-arguments" + }, + { + "include": "#type-array-suffix" + }, + { + "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\b\\s*", - "beginCaptures": { - "1": { + }, + { + "begin": "(?<=\\})", + "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", "patterns": [ { - "include": "#type" - } - ] - }, - "2": { - "name": "entity.name.variable.local.cs" - } - }, - "end": "(?==>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#switch-when-clause" - } - ] - }, - "switch-property-expression": { - "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(\\{)", - "beginCaptures": { - "1": { - "patterns": [ + "include": "#intrusive" + }, { - "include": "#type" - } - ] - }, - "6": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "switch-var-pattern": { - "begin": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*", - "beginCaptures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#tuple-declaration-deconstruction-element-list" + "include": "#simple-designation-pattern" } ] } - }, - "end": "(?==>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#switch-when-clause" - } ] }, - "switch-when-clause": { - "begin": "(?)", + "subpattern": { "patterns": [ { - "include": "#comment" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - }, - { - "match": "\\(", - "captures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - } - }, - { - "match": "\\)", - "captures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - } - } - ] - }, - "switch-label": { - "patterns": [ - { - "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2176,7 +2719,7 @@ "match": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)?\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2194,9 +2737,6 @@ "include": "#expression" } ] - }, - { - "include": "#statement" } ] }, @@ -2217,7 +2757,7 @@ "begin": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", + "begin": "(?x)\n(?:\n (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*[?*]\\s*)? # nullable or pointer suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", "beginCaptures": { "1": { - "name": "keyword.other.using.cs" + "name": "storage.modifier.ref.cs" }, "2": { - "name": "storage.modifier.cs" + "name": "storage.modifier.readonly.cs" }, "3": { - "name": "storage.modifier.cs" + "name": "storage.type.var.cs" }, "4": { - "name": "keyword.other.var.cs" - }, - "5": { "patterns": [ { "include": "#type" } ] }, - "10": { + "9": { "name": "entity.name.variable.local.cs" } }, - "end": "(?=;|\\))", + "end": "(?=[;)}])", "patterns": [ { "name": "entity.name.variable.local.cs", @@ -2486,7 +3068,7 @@ "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.const.cs" }, "2": { "patterns": [ @@ -2517,9 +3099,49 @@ ] }, "local-function-declaration": { + "begin": "(?x)\n\\b((?:(?:async|unsafe|static|extern)\\s+)*)\n(?\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?)? # arrays can be nullable reference types\n )*\n)\\s+\n(\\g)\\s*\n(<[^<>]+>)?\\s*\n(?=\\()", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#storage-modifier" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#type" + } + ] + }, + "7": { + "name": "entity.name.function.cs" + }, + "8": { + "patterns": [ + { + "include": "#type-parameter-list" + } + ] + } + }, + "end": "(?<=\\})|(?=;)", "patterns": [ { - "include": "#method-declaration" + "include": "#comment" + }, + { + "include": "#parenthesized-parameter-list" + }, + { + "include": "#generic-constraints" + }, + { + "include": "#expression-body" + }, + { + "include": "#block" } ] }, @@ -2527,7 +3149,7 @@ "begin": "(?x) # e.g. var (x, y) = GetPoint();\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?=;|=|\\))", "beginCaptures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2635,7 +3257,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2653,7 +3275,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "keyword.other.var.cs" + "name": "storage.type.var.cs" }, "2": { "patterns": [ @@ -2667,16 +3289,13 @@ } } }, - "checked-unchecked-expression": { - "begin": "(?>>?|\\|)?=(?!=|>)", + "beginCaptures": { + "0": { + "patterns": [ + { + "include": "#assignment-operators" + } + ] + } + }, + "end": "(?=[,\\)\\];}])", + "patterns": [ + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] + }, + "assignment-operators": { "patterns": [ { "name": "keyword.operator.assignment.compound.cs", @@ -3391,11 +4006,19 @@ }, { "name": "keyword.operator.assignment.compound.bitwise.cs", - "match": "\\&=|\\^=|<<=|>>=|\\|=" + "match": "\\&=|\\^=|<<=|>>>?=|\\|=" }, + { + "name": "keyword.operator.assignment.cs", + "match": "\\=" + } + ] + }, + "expression-operators": { + "patterns": [ { "name": "keyword.operator.bitwise.shift.cs", - "match": "<<|>>" + "match": "<<|>>>?" }, { "name": "keyword.operator.comparison.cs", @@ -3413,10 +4036,6 @@ "name": "keyword.operator.bitwise.cs", "match": "\\&|~|\\^|\\|" }, - { - "name": "keyword.operator.assignment.cs", - "match": "\\=" - }, { "name": "keyword.operator.decrement.cs", "match": "--" @@ -3427,56 +4046,50 @@ }, { "name": "keyword.operator.arithmetic.cs", - "match": "%|\\*|/|-|\\+" + "match": "\\+|-(?!>)|\\*|/|%" }, { "name": "keyword.operator.null-coalescing.cs", "match": "\\?\\?" + }, + { + "name": "keyword.operator.range.cs", + "match": "\\.\\." } ] }, - "switch-literal": { - "name": "constant.language.null.cs", - "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?(?!\\?))? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?(?!\\?))? # arrays can be nullable reference types\n )*\n )\n)?", "captures": { "1": { - "name": "keyword.other.as.cs" + "name": "keyword.operator.expression.as.cs" }, "2": { "patterns": [ @@ -3556,34 +4169,20 @@ } } }, - "is-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", - "captures": { - "1": { - "name": "keyword.other.is.cs" + "language-variable": { + "patterns": [ + { + "name": "variable.language.$1.cs", + "match": "\\b(base|this)\\b" }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] + { + "name": "variable.other.$1.cs", + "match": "\\b(value)\\b" } - } - }, - "this-or-base-expression": { - "match": "\\b(?:(base)|(this))\\b", - "captures": { - "1": { - "name": "keyword.other.base.cs" - }, - "2": { - "name": "keyword.other.this.cs" - } - } + ] }, "invocation-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", + "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(\n <\n (?\n [^<>()]+|\n <\\g+>|\n \\(\\g+\\)\n )+\n >\\s*\n)? # type arguments\n(?=\\() # open paren of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3592,9 +4191,12 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "entity.name.function.cs" + "name": "punctuation.accessor.pointer.cs" }, "4": { + "name": "entity.name.function.cs" + }, + "5": { "patterns": [ { "include": "#type-arguments" @@ -3610,7 +4212,7 @@ ] }, "element-access-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", + "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3619,9 +4221,12 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "variable.other.object.property.cs" + "name": "punctuation.accessor.pointer.cs" }, "4": { + "name": "variable.other.object.property.cs" + }, + "5": { "name": "keyword.operator.null-conditional.cs" } }, @@ -3635,7 +4240,7 @@ "member-access-expression": { "patterns": [ { - "match": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(\\.)\\s* # preceding dot\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", + "match": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", "captures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -3644,6 +4249,9 @@ "name": "punctuation.accessor.cs" }, "3": { + "name": "punctuation.accessor.pointer.cs" + }, + "4": { "name": "variable.other.object.property.cs" } } @@ -3667,7 +4275,7 @@ } }, { - "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", + "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n \\s*(?:(?:\\?\\s*)?\\.|->)\n \\s*@?[_[:alpha:]][_[:alnum:]]*\n)", "captures": { "1": { "name": "variable.other.object.cs" @@ -3690,7 +4298,7 @@ "begin": "(?x)\n(new)(?:\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n))?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "patterns": [ @@ -3708,10 +4316,10 @@ ] }, "object-creation-expression-with-no-parameters": { - "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|$)", + "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|//|/\\*|$)", "captures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" }, "2": { "patterns": [ @@ -3726,7 +4334,7 @@ "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(?=\\[)", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.$1.cs" }, "2": { "patterns": [ @@ -3744,14 +4352,17 @@ ] }, "anonymous-object-creation-expression": { - "begin": "\\b(new)\\b\\s*(?=\\{|$)", + "begin": "\\b(new)\\b\\s*(?=\\{|//|/\\*|$)", "beginCaptures": { "1": { - "name": "keyword.other.new.cs" + "name": "keyword.operator.expression.new.cs" } }, "end": "(?<=\\})", "patterns": [ + { + "include": "#comment" + }, { "include": "#initializer-expression" } @@ -3829,7 +4440,7 @@ "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", "captures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.$1.cs" }, "2": { "patterns": [ @@ -3913,11 +4524,25 @@ "argument": { "patterns": [ { - "name": "storage.modifier.cs", - "match": "\\b(ref|out|in)\\b" + "name": "storage.modifier.$1.cs", + "match": "\\b(ref|in)\\b" }, { - "include": "#declaration-expression-local" + "begin": "\\b(out)\\b", + "beginCaptures": { + "1": { + "name": "storage.modifier.out.cs" + } + }, + "end": "(?=,|\\)|\\])", + "patterns": [ + { + "include": "#declaration-expression-local" + }, + { + "include": "#expression" + } + ] }, { "include": "#expression" @@ -3928,7 +4553,7 @@ "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.from.cs" + "name": "keyword.operator.expression.query.from.cs" }, "2": { "patterns": [ @@ -3941,7 +4566,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.query.in.cs" + "name": "keyword.operator.expression.query.in.cs" } }, "end": "(?=;|\\))", @@ -3980,7 +4605,7 @@ "begin": "(?x)\n\\b(let)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=)\\s*", "beginCaptures": { "1": { - "name": "keyword.query.let.cs" + "name": "keyword.operator.expression.query.let.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4003,7 +4628,7 @@ "begin": "(?x)\n\\b(where)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.where.cs" + "name": "keyword.operator.expression.query.where.cs" } }, "end": "(?=;|\\))", @@ -4020,7 +4645,7 @@ "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.join.cs" + "name": "keyword.operator.expression.query.join.cs" }, "2": { "patterns": [ @@ -4033,7 +4658,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.query.in.cs" + "name": "keyword.operator.expression.query.in.cs" } }, "end": "(?=;|\\))", @@ -4059,7 +4684,7 @@ "match": "\\b(on)\\b\\s*", "captures": { "1": { - "name": "keyword.query.on.cs" + "name": "keyword.operator.expression.query.on.cs" } } }, @@ -4067,7 +4692,7 @@ "match": "\\b(equals)\\b\\s*", "captures": { "1": { - "name": "keyword.query.equals.cs" + "name": "keyword.operator.expression.query.equals.cs" } } }, @@ -4075,7 +4700,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.query.into.cs" + "name": "keyword.operator.expression.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4086,7 +4711,7 @@ "begin": "\\b(orderby)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.orderby.cs" + "name": "keyword.operator.expression.query.orderby.cs" } }, "end": "(?=;|\\))", @@ -4106,13 +4731,10 @@ ] }, "ordering-direction": { - "match": "\\b(?:(ascending)|(descending))\\b", + "match": "\\b(ascending|descending)\\b", "captures": { "1": { - "name": "keyword.query.ascending.cs" - }, - "2": { - "name": "keyword.query.descending.cs" + "name": "keyword.operator.expression.query.$1.cs" } } }, @@ -4120,7 +4742,7 @@ "begin": "\\b(select)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.select.cs" + "name": "keyword.operator.expression.query.select.cs" } }, "end": "(?=;|\\))", @@ -4137,7 +4759,7 @@ "begin": "\\b(group)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.query.group.cs" + "name": "keyword.operator.expression.query.group.cs" } }, "end": "(?=;|\\))", @@ -4160,7 +4782,7 @@ "match": "\\b(by)\\b\\s*", "captures": { "1": { - "name": "keyword.query.by.cs" + "name": "keyword.operator.expression.query.by.cs" } } }, @@ -4168,7 +4790,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.query.into.cs" + "name": "keyword.operator.expression.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4178,10 +4800,15 @@ "anonymous-method-expression": { "patterns": [ { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { "name": "entity.name.variable.parameter.cs" @@ -4204,10 +4831,15 @@ ] }, { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(\\(.*?\\))\\s*\n(=>)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(?\n \\(\n (?:[^()]|\\g)*\n \\)\n)\\s*\n(=>)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { "patterns": [ @@ -4234,13 +4866,18 @@ ] }, { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(?:\\b(delegate)\\b\\s*)", + "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(?:\\b(delegate)\\b\\s*)", "beginCaptures": { "1": { - "name": "storage.modifier.cs" + "patterns": [ + { + "match": "async|static", + "name": "storage.modifier.$0.cs" + } + ] }, "2": { - "name": "keyword.other.delegate.cs" + "name": "storage.type.delegate.cs" } }, "end": "(?=\\)|;|}|,)", @@ -4250,9 +4887,6 @@ }, { "include": "#block" - }, - { - "include": "#expression" } ] } @@ -4290,7 +4924,7 @@ "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "storage.modifier.cs" + "name": "storage.modifier.$1.cs" }, "2": { "patterns": [ @@ -4305,7 +4939,6 @@ } }, "type": { - "name": "meta.type.cs", "patterns": [ { "include": "#comment" @@ -4333,16 +4966,19 @@ }, { "include": "#type-nullable-suffix" + }, + { + "include": "#type-pointer-suffix" } ] }, "ref-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(ref)\\b" + "name": "storage.modifier.ref.cs", + "match": "\\bref\\b" }, "readonly-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(readonly)\\b" + "name": "storage.modifier.readonly.cs", + "match": "\\breadonly\\b" }, "tuple-type": { "begin": "\\(", @@ -4382,10 +5018,10 @@ } }, "type-builtin": { - "match": "\\b(bool|byte|char|decimal|double|float|int|long|object|sbyte|short|string|uint|ulong|ushort|void|dynamic)\\b", + "match": "\\b(bool|s?byte|u?short|n?u?int|u?long|float|double|decimal|char|string|object|void|dynamic)\\b", "captures": { "1": { - "name": "keyword.type.cs" + "name": "keyword.type.$1.cs" } } }, @@ -4444,9 +5080,6 @@ } }, "patterns": [ - { - "include": "#comment" - }, { "include": "#type" }, @@ -4469,6 +5102,9 @@ } }, "patterns": [ + { + "include": "#intrusive" + }, { "include": "#punctuation-comma" } @@ -4476,11 +5112,11 @@ }, "type-nullable-suffix": { "match": "\\?", - "captures": { - "0": { - "name": "punctuation.separator.question-mark.cs" - } - } + "name": "punctuation.separator.question-mark.cs" + }, + "type-pointer-suffix": { + "match": "\\*", + "name": "punctuation.separator.asterisk.cs" }, "operator-assignment": { "name": "keyword.operator.assignment.cs", @@ -4498,6 +5134,16 @@ "name": "punctuation.accessor.cs", "match": "\\." }, + "intrusive": { + "patterns": [ + { + "include": "#preprocessor" + }, + { + "include": "#comment" + } + ] + }, "preprocessor": { "name": "meta.preprocessor.cs", "begin": "^\\s*(\\#)\\s*", @@ -4803,38 +5449,47 @@ "comment": { "patterns": [ { - "name": "comment.block.cs", - "begin": "/\\*", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.cs" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.cs" - } - } - }, - { - "begin": "(^\\s+)?(?=//)", - "beginCaptures": { + "name": "comment.block.documentation.cs", + "begin": "(^\\s+)?(///)(?!/)", + "while": "^(\\s*)(///)(?!/)", + "captures": { "1": { "name": "punctuation.whitespace.comment.leading.cs" + }, + "2": { + "name": "punctuation.definition.comment.cs" } }, - "end": "(?=$)", "patterns": [ { - "name": "comment.block.documentation.cs", - "begin": "(?emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; - return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + assert.ok(result1); + assert.ok(result2); callBack(result1, '#121212'); callBack(result2, '!important'); editor.selections = [new Selection(2, 12, 2, 12), new Selection(2, 14, 2, 14)]; @@ -244,7 +246,9 @@ nav# assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; - return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + assert.ok(result1); + assert.ok(result2); callBack(result1, '#121212'); callBack(result2, '!important'); editor.selections = [new Selection(3, 12, 3, 12), new Selection(3, 14, 3, 14)]; @@ -303,7 +307,9 @@ nav# assert.strictEqual((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); }; - return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + assert.ok(result1); + assert.ok(result2); callBack(result1, '#121212'); callBack(result2, '!important'); editor.selections = [new Selection(2, 12, 2, 12), new Selection(2, 14, 2, 14)]; @@ -361,7 +367,9 @@ nav# assert.strictEqual(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); }; - return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + assert.ok(result1); + assert.ok(result2); callBack(result1); callBack(result2); return Promise.resolve(); @@ -421,7 +429,11 @@ nav# assert.strictEqual(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); }; - return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { + return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { + assert.ok(result1); + assert.ok(result2); + assert.ok(result3); + assert.ok(result4); callBack(result1, 'p10', 'padding: 10px;'); callBack(result2, 'p20', 'padding: 20px;'); callBack(result3, 'p30', 'padding: 30px;'); diff --git a/extensions/emmet/src/updateTag.ts b/extensions/emmet/src/updateTag.ts index 1d0a2971c06..c47f18d46ea 100644 --- a/extensions/emmet/src/updateTag.ts +++ b/extensions/emmet/src/updateTag.ts @@ -25,8 +25,8 @@ export async function updateTag(tagName: string | undefined): Promise((prev, selection) => + const rangesToUpdate = editor.selections + .reduceRight((prev, selection) => prev.concat(getRangesToUpdate(document, selection, rootNode)), []); if (!rangesToUpdate.length) { return; diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 5d6d342783f..f45105b99d4 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -26,7 +26,7 @@ "watch": "gulp watch-extension:extension-editing" }, "dependencies": { - "jsonc-parser": "^2.2.1", + "jsonc-parser": "^3.2.0", "markdown-it": "^12.3.2", "parse5": "^3.0.2" }, diff --git a/extensions/extension-editing/src/constants.ts b/extensions/extension-editing/src/constants.ts new file mode 100644 index 00000000000..1be4d0e12e5 --- /dev/null +++ b/extensions/extension-editing/src/constants.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { l10n } from 'vscode'; + +export const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension."); +export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations."); diff --git a/extensions/extension-editing/src/extensionEditingMain.ts b/extensions/extension-editing/src/extensionEditingMain.ts index 9dc71a46896..c056fbfa975 100644 --- a/extensions/extension-editing/src/extensionEditingMain.ts +++ b/extensions/extension-editing/src/extensionEditingMain.ts @@ -12,10 +12,12 @@ export function activate(context: vscode.ExtensionContext) { //package.json suggestions context.subscriptions.push(registerPackageDocumentCompletions()); + //package.json code actions for lint warnings + context.subscriptions.push(registerCodeActionsProvider()); + context.subscriptions.push(new ExtensionLinter()); } - function registerPackageDocumentCompletions(): vscode.Disposable { return vscode.languages.registerCompletionItemProvider({ language: 'json', pattern: '**/package.json' }, { provideCompletionItems(document, position, token) { @@ -23,3 +25,11 @@ function registerPackageDocumentCompletions(): vscode.Disposable { } }); } + +function registerCodeActionsProvider(): vscode.Disposable { + return vscode.languages.registerCodeActionsProvider({ language: 'json', pattern: '**/package.json' }, { + provideCodeActions(document, range, context, token) { + return new PackageDocument(document).provideCodeActions(range, context, token); + } + }); +} diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 459bd9a8e16..8bb2a4640df 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -13,6 +13,7 @@ import * as MarkdownItType from 'markdown-it'; import { commands, languages, workspace, Disposable, TextDocument, Uri, Diagnostic, Range, DiagnosticSeverity, Position, env, l10n } from 'vscode'; import { INormalizedVersion, normalizeVersion, parseVersion } from './extensionEngineValidation'; import { JsonStringScanner } from './jsonReconstruct'; +import { implicitActivationEvent, redundantImplicitActivationEvent } from './constants'; const product = JSON.parse(fs.readFileSync(path.join(env.appRoot, 'product.json'), { encoding: 'utf-8' })); const allowedBadgeProviders: string[] = (product.extensionAllowedBadgeProviders || []).map((s: string) => s.toLowerCase()); @@ -32,8 +33,6 @@ const dataUrlsNotValid = l10n.t("Data URLs are not a valid image source."); const relativeUrlRequiresHttpsRepository = l10n.t("Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); -const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension."); -const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations."); const bumpEngineForImplicitActivationEvents = l10n.t("This activation event can be removed for extensions targeting engine version ^1.75 as VS Code will generate these automatically from your package.json contribution declarations."); const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance."); const parsingErrorHeader = l10n.t("Error parsing the when-clause:"); @@ -128,7 +127,7 @@ export class ExtensionLinter { const tree = parseTree(document.getText()); const info = this.readPackageJsonInfo(this.getUriFolder(document.uri), tree); - if (info.isExtension) { + if (tree && info.isExtension) { const icon = findNodeAtLocation(tree, ['icon']); if (icon && icon.type === 'string') { diff --git a/extensions/extension-editing/src/packageDocumentHelper.ts b/extensions/extension-editing/src/packageDocumentHelper.ts index 67163900b5b..5febab27ea9 100644 --- a/extensions/extension-editing/src/packageDocumentHelper.ts +++ b/extensions/extension-editing/src/packageDocumentHelper.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { getLocation, Location } from 'jsonc-parser'; +import { implicitActivationEvent, redundantImplicitActivationEvent } from './constants'; export class PackageDocument { @@ -21,6 +22,24 @@ export class PackageDocument { return undefined; } + public provideCodeActions(_range: vscode.Range, context: vscode.CodeActionContext, _token: vscode.CancellationToken): vscode.ProviderResult { + const codeActions: vscode.CodeAction[] = []; + for (const diagnostic of context.diagnostics) { + if (diagnostic.message === implicitActivationEvent || diagnostic.message === redundantImplicitActivationEvent) { + const codeAction = new vscode.CodeAction(vscode.l10n.t("Remove activation event"), vscode.CodeActionKind.QuickFix); + codeAction.edit = new vscode.WorkspaceEdit(); + const rangeForCharAfter = diagnostic.range.with(diagnostic.range.end, diagnostic.range.end.translate(0, 1)); + if (this.document.getText(rangeForCharAfter) === ',') { + codeAction.edit.delete(this.document.uri, diagnostic.range.with(undefined, diagnostic.range.end.translate(0, 1))); + } else { + codeAction.edit.delete(this.document.uri, diagnostic.range); + } + codeActions.push(codeAction); + } + } + return codeActions; + } + private provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): vscode.ProviderResult { let range = this.getReplaceRange(location, position); const text = this.document.getText(range); diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index ff42c956777..5456b3ec040 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -27,15 +27,15 @@ entities@~2.1.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== -jsonc-parser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== linkify-it@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" - integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: uc.micro "^1.0.1" diff --git a/extensions/git/package.json b/extensions/git/package.json index 9ad1978be0e..f236c524faf 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -2465,7 +2465,7 @@ "null" ], "default": 50, - "description": "%config.inputValidationSubjectLength%" + "markdownDescription": "%config.inputValidationSubjectLength%" }, "git.detectSubmodules": { "type": "boolean", @@ -2659,7 +2659,7 @@ "description": "%config.useIntegratedAskPass%" }, "git.githubAuthentication": { - "deprecationMessage": "This setting is now deprecated, please use `github.gitAuthentication` instead." + "markdownDeprecationMessage": "This setting is now deprecated, please use `#github.gitAuthentication#` instead." }, "git.timeline.date": { "type": "string", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 24f03414ce6..de8f10e7174 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -191,7 +191,7 @@ "config.showPushSuccessNotification": "Controls whether to show a notification when a push is successful.", "config.inputValidation": "Controls when to show commit message input validation.", "config.inputValidationLength": "Controls the commit message length threshold for showing a warning.", - "config.inputValidationSubjectLength": "Controls the commit message subject length threshold for showing a warning. Unset it to inherit the value of `config.inputValidationLength`.", + "config.inputValidationSubjectLength": "Controls the commit message subject length threshold for showing a warning. Unset it to inherit the value of `#git.inputValidationLength#`.", "config.detectSubmodules": "Controls whether to automatically detect git submodules.", "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", "config.alwaysShowStagedChangesResourceGroup": "Always show the Staged Changes resource group.", diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 03a6ccf18ad..fef934fd506 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -495,7 +495,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.logger.trace(`Opening repository: ${repoPath}`); const existingRepository = await this.getRepositoryExact(repoPath); if (existingRepository) { - this.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root})`); + this.logger.trace(`Repository for path ${repoPath} already exists: ${existingRepository.root}`); return; } diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 422fce5e7f6..a18aed6e955 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -54,7 +54,7 @@ "%html.completion.attributeDefaultValue.empty%" ], "default": "doublequotes", - "description": "%html.completion.attributeDefaultValue%" + "markdownDescription": "%html.completion.attributeDefaultValue%" }, "html.customData": { "type": "array", diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index acb6474d63e..f36ecf34f02 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -21,7 +21,7 @@ "html.format.wrapAttributes.preservealigned": "Preserve wrapping of attributes but align.", "html.format.templating.desc": "Honor django, erb, handlebars and php templating language tags.", "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", - "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to 'aligned'.", + "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to `aligned`.", "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index be9e7439fe7..72e0c5c882b 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "redhat-developer/vscode-java", "repositoryUrl": "https://github.com/redhat-developer/vscode-java", - "commitHash": "5fb57e8e1c5d776b21be13cd7227b25b87edf4a6" + "commitHash": "5d224a552cf5f0f8ebccf69e43e2575ed2c13839" } }, "license": "MIT", @@ -44,7 +44,7 @@ "suitability for any purpose." ], "description": "This grammar was derived from https://github.com/atom/language-java/blob/master/grammars/java.cson.", - "version": "1.21.0" + "version": "1.22.0" } ], "version": 1 diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index 337545b0233..7f969ccb787 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.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/redhat-developer/vscode-java/commit/5fb57e8e1c5d776b21be13cd7227b25b87edf4a6", + "version": "https://github.com/redhat-developer/vscode-java/commit/5d224a552cf5f0f8ebccf69e43e2575ed2c13839", "name": "Java", "scopeName": "source.java", "patterns": [ diff --git a/extensions/julia/cgmanifest.json b/extensions/julia/cgmanifest.json index 0dac126a5ae..d546ac2db12 100644 --- a/extensions/julia/cgmanifest.json +++ b/extensions/julia/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "JuliaEditorSupport/atom-language-julia", "repositoryUrl": "https://github.com/JuliaEditorSupport/atom-language-julia", - "commitHash": "ccc0277c9ee9af34a0b50e5fa27a6f5191601b8c" + "commitHash": "7cbe6a7c4f2c8275e15f5b6e0722d285730ffb99" } }, "license": "MIT", diff --git a/extensions/julia/syntaxes/julia.tmLanguage.json b/extensions/julia/syntaxes/julia.tmLanguage.json index c4e146ee5e7..66998399c4b 100644 --- a/extensions/julia/syntaxes/julia.tmLanguage.json +++ b/extensions/julia/syntaxes/julia.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/JuliaEditorSupport/atom-language-julia/commit/ccc0277c9ee9af34a0b50e5fa27a6f5191601b8c", + "version": "https://github.com/JuliaEditorSupport/atom-language-julia/commit/7cbe6a7c4f2c8275e15f5b6e0722d285730ffb99", "name": "Julia", "scopeName": "source.julia", "comment": "This grammar is used by Atom (Oniguruma), GitHub (PCRE), and VSCode (Oniguruma),\nso all regexps must be compatible with both engines.\n\nSpecs:\n- https://github.com/kkos/oniguruma/blob/master/doc/RE\n- https://www.pcre.org/current/doc/html/", @@ -356,13 +356,16 @@ "name": "keyword.operator.shift.julia" }, { - "match": "(?:\\s*(::|>:|<:)\\s*((?:(?:Union)?\\([^)]*\\)|[[:alpha:]_$∇][[:word:]⁺-ₜ!′\\.]*(?:(?:{(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})|(?:\".+?(?:|<:)\\s*((?:(?:Union)?\\([^)]*\\)|[[:alpha:]_$∇][[:word:]⁺-ₜ!′\\.]*(?:(?:{(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})|(?:\".+?(?\\?\\[\\]\\^\\_\\`\\|]+)", + "begin": "((@)(?i:preamble))\\s*(\\()\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.preamble.bibtex" + }, + "2": { + "name": "punctuation.definition.keyword.bibtex" + }, + "3": { + "name": "punctuation.section.preamble.begin.bibtex" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.preamble.end.bibtex" + } + }, + "name": "meta.preamble.parenthesis.bibtex", + "patterns": [ + { + "include": "#field_value" + } + ] + }, + { + "begin": "((@)(?i:string))\\s*(\\{)\\s*([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)", "beginCaptures": { "1": { "name": "keyword.other.string-constant.bibtex" @@ -51,12 +95,12 @@ "name": "meta.string-constant.braces.bibtex", "patterns": [ { - "include": "#string_content" + "include": "#field_value" } ] }, { - "begin": "((@)(?i:string))\\s*(\\()\\s*([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)", + "begin": "((@)(?i:string))\\s*(\\()\\s*([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)", "beginCaptures": { "1": { "name": "keyword.other.string-constant.bibtex" @@ -80,12 +124,12 @@ "name": "meta.string-constant.parenthesis.bibtex", "patterns": [ { - "include": "#string_content" + "include": "#field_value" } ] }, { - "begin": "((@)[a-zA-Z]+)\\s*(\\{)\\s*([^\\s,]*)", + "begin": "((@)[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\{)\\s*([^\\s,}]*)", "beginCaptures": { "1": { "name": "keyword.other.entry-type.bibtex" @@ -109,13 +153,7 @@ "name": "meta.entry.braces.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#url_field" - }, - { - "begin": "([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)\\s*(\\=)", + "begin": "([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\=)", "beginCaptures": { "1": { "name": "support.function.key.bibtex" @@ -128,23 +166,14 @@ "name": "meta.key-assignment.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#integer" - }, - { - "include": "#string_content" - }, - { - "include": "#string_var" + "include": "#field_value" } ] } ] }, { - "begin": "((@)[a-zA-Z]+)\\s*(\\()\\s*([^\\s,]*)", + "begin": "((@)[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\()\\s*([^\\s,]*)", "beginCaptures": { "1": { "name": "keyword.other.entry-type.bibtex" @@ -168,13 +197,7 @@ "name": "meta.entry.parenthesis.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#url_field" - }, - { - "begin": "([a-zA-Z0-9\\!\\$\\&\\*\\+\\-\\.\\/\\:\\;\\<\\>\\?\\[\\]\\^\\_\\`\\|]+)\\s*(\\=)", + "begin": "([a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*)\\s*(\\=)", "beginCaptures": { "1": { "name": "support.function.key.bibtex" @@ -187,16 +210,7 @@ "name": "meta.key-assignment.bibtex", "patterns": [ { - "include": "#percentage_comment" - }, - { - "include": "#integer" - }, - { - "include": "#string_content" - }, - { - "include": "#string_var" + "include": "#field_value" } ] } @@ -209,6 +223,23 @@ } ], "repository": { + "field_value": { + "patterns": [ + { + "include": "#string_content" + }, + { + "include": "#integer" + }, + { + "include": "#string_var" + }, + { + "name": "keyword.operator.bibtex", + "match": "#" + } + ] + }, "integer": { "match": "\\s*(\\d+)\\s*", "captures": { @@ -218,13 +249,13 @@ } }, "nested_braces": { - "begin": "(?\\?\\[\\]\\^\\_\\`\\|]+)\\s*(#)?", + "match": "[a-zA-Z!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~][a-zA-Z0-9!$&*+\\-./:;<>?@\\[\\\\\\]^_`|~]*", "captures": { - "1": { - "name": "keyword.operator.bibtex" - }, - "2": { + "0": { "name": "support.variable.bibtex" - }, - "3": { - "name": "keyword.operator.bibtex" } } }, @@ -259,23 +284,13 @@ "name": "punctuation.definition.string.begin.bibtex" } }, - "end": "(\\})(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", + "end": "\\}", "endCaptures": { - "1": { + "0": { "name": "punctuation.definition.string.end.bibtex" } }, "patterns": [ - { - "include": "#url_cmd" - }, - { - "include": "#percentage_comment" - }, - { - "match": "@", - "name": "invalid.illegal.at-sign.bibtex" - }, { "include": "#nested_braces" } @@ -288,7 +303,7 @@ "name": "punctuation.definition.string.begin.bibtex" } }, - "end": "\"(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", + "end": "\"", "endCaptures": { "0": { "name": "punctuation.definition.string.end.bibtex" @@ -296,106 +311,11 @@ }, "patterns": [ { - "include": "#url_cmd" - }, - { - "include": "#percentage_comment" - }, - { - "match": "@", - "name": "invalid.illegal.at-sign.bibtex" + "include": "#nested_braces" } ] } ] - }, - "string_url": { - "patterns": [ - { - "begin": "\\{|\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.bibtex" - } - }, - "end": "(\\}|\")(?=(?:,?\\s*\\}?\\s*\\n)|(?:\\s*#))", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.bibtex" - } - }, - "contentName": "meta.url.bibtex", - "patterns": [ - { - "include": "#url_cmd" - } - ] - } - ] - }, - "percentage_comment": { - "patterns": [ - { - "begin": "(^[ \\t]+)?(?=%)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.bibtex" - } - }, - "end": "(?!\\G)", - "patterns": [ - { - "begin": "(? = new Map(); - private _refreshingPromise: Promise | undefined; private _sessionChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter(); // Used to keep track of current requests when not using the local server approach. @@ -96,6 +97,12 @@ export class AzureActiveDirectoryService { private _codeExchangePromises = new Map>(); private _codeVerfifiers = new Map(); + // Used to keep track of tokens that we need to store but can't because we aren't the focused window. + private _pendingTokensToStore: Map = new Map(); + + // Used to sequence requests to the same scope. + private _sequencer = new SequencerByKey(); + constructor( private readonly _logger: vscode.LogOutputChannel, _context: vscode.ExtensionContext, @@ -105,15 +112,24 @@ export class AzureActiveDirectoryService { private readonly _env: Environment ) { _context.subscriptions.push(this._tokenStorage.onDidChangeInOtherWindow((e) => this.checkForUpdates(e))); + _context.subscriptions.push(vscode.window.onDidChangeWindowState(async (e) => e.focused && await this.storePendingTokens())); + + // In the event that a window isn't focused for a long time, we should still try to store the tokens at some point. + const timer = new IntervalTimer(); + timer.cancelAndSet( + () => !vscode.window.state.focused && this.storePendingTokens(), + // 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time + (18000000) + Math.floor(Math.random() * 30000)); + _context.subscriptions.push(timer); } public async initialize(): Promise { - this._logger.info('Reading sessions from secret storage...'); + this._logger.trace('Reading sessions from secret storage...'); const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item)); - this._logger.info(`Got ${sessions.length} stored sessions`); + this._logger.trace(`Got ${sessions.length} stored sessions`); const refreshes = sessions.map(async session => { - this._logger.trace(`Read the following stored session with scopes: ${session.scope}`); + this._logger.trace(`[${session.scope}] '${session.id}' Read stored session`); const scopes = session.scope.split(' '); const scopeData: IScopeData = { scopes, @@ -187,12 +203,12 @@ export class AzureActiveDirectoryService { return this._sessionChangeEmitter.event; } - async getSessions(scopes?: string[]): Promise { + public getSessions(scopes?: string[]): Promise { if (!scopes) { this._logger.info('Getting sessions for all scopes...'); const sessions = this._tokens.map(token => this.convertToSessionSync(token)); this._logger.info(`Got ${sessions.length} sessions for all scopes...`); - return sessions; + return Promise.resolve(sessions); } let modifiedScopes = [...scopes]; @@ -210,32 +226,7 @@ export class AzureActiveDirectoryService { } modifiedScopes = modifiedScopes.sort(); - let modifiedScopesStr = modifiedScopes.join(' '); - this._logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`); - - if (this._refreshingPromise) { - this._logger.info('Refreshing in progress. Waiting for completion before continuing.'); - try { - await this._refreshingPromise; - } catch (e) { - // this will get logged in the refresh function. - } - } - - let matchingTokens = this._tokens.filter(token => token.scope === modifiedScopesStr); - - // The user may still have a token that doesn't have the openid & email scopes so check for that as well. - // Eventually, we should remove this and force the user to re-log in so that we don't have any sessions - // without an idtoken. - if (!matchingTokens.length) { - const fallbackOrderedScopes = scopes.sort().join(' '); - this._logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`); - matchingTokens = this._tokens.filter(token => token.scope === fallbackOrderedScopes); - if (matchingTokens.length) { - modifiedScopesStr = fallbackOrderedScopes; - } - } - + const modifiedScopesStr = modifiedScopes.join(' '); const clientId = this.getClientId(scopes); const scopeData: IScopeData = { clientId, @@ -247,31 +238,44 @@ export class AzureActiveDirectoryService { tenant: this.getTenantId(scopes), }; + this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions`); + return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData)); + } + + private async doGetSessions(scopeData: IScopeData): Promise { + this._logger.info(`[${scopeData.scopeStr}] Getting sessions`); + + const matchingTokens = this._tokens.filter(token => token.scope === scopeData.scopeStr); // If we still don't have a matching token try to get a new token from an existing token by using // the refreshToken. This is documented here: // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token // "Refresh tokens are valid for all permissions that your client has already received consent for." if (!matchingTokens.length) { // Get a token with the correct client id. - const token = clientId === DEFAULT_CLIENT_ID + const token = scopeData.clientId === DEFAULT_CLIENT_ID ? this._tokens.find(t => t.refreshToken && !t.scope.includes('VSCODE_CLIENT_ID')) - : this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${clientId}`)); + : this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)); if (token) { + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Found a matching token with a different scopes '${token.scope}'. Attempting to get a new session using the existing session.`); try { - const itoken = await this.refreshToken(token.refreshToken, scopeData); + const itoken = await this.doRefreshToken(token.refreshToken, scopeData); + this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(itoken)], removed: [], changed: [] }); matchingTokens.push(itoken); } catch (err) { - this._logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); + this._logger.error(`[${scopeData.scopeStr}] Attempted to get a new session using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); } } } - this._logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); - return Promise.all(matchingTokens.map(token => this.convertToSession(token, scopeData))); + this._logger.info(`[${scopeData.scopeStr}] Got ${matchingTokens.length} sessions`); + const results = await Promise.allSettled(matchingTokens.map(token => this.convertToSession(token, scopeData))); + return results + .filter(result => result.status === 'fulfilled') + .map(result => (result as PromiseFulfilledResult).value); } - public async createSession(scopes: string[]): Promise { + public createSession(scopes: string[]): Promise { let modifiedScopes = [...scopes]; if (!modifiedScopes.includes('openid')) { modifiedScopes.push('openid'); @@ -296,7 +300,12 @@ export class AzureActiveDirectoryService { tenant: this.getTenantId(scopes), }; - this._logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] Queued creating session`); + return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData)); + } + + private async doCreateSession(scopeData: IScopeData): Promise { + this._logger.info(`[${scopeData.scopeStr}] Creating session`); const runsRemote = vscode.env.remoteName !== undefined; const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; @@ -312,7 +321,7 @@ export class AzureActiveDirectoryService { try { return await this.createSessionWithLocalServer(scopeData); } catch (e) { - this._logger.error(`Error creating session for scopes: ${scopeData.scopeStr} Error: ${e}`); + this._logger.error(`[${scopeData.scopeStr}] Error creating session: ${e}`); // If the error was about starting the server, try directly hitting the login endpoint instead if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { @@ -324,6 +333,7 @@ export class AzureActiveDirectoryService { } private async createSessionWithLocalServer(scopeData: IScopeData) { + this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with local server`); const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); const qs = new URLSearchParams({ @@ -352,11 +362,14 @@ export class AzureActiveDirectoryService { } const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData); + this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Sending change event for added session`); this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); + this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); return session; } private async createSessionWithoutLocalServer(scopeData: IScopeData): Promise { + this._logger.trace(`[${scopeData.scopeStr}] Starting login flow without local server`); let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); const nonce = generateCodeVerifier(); const callbackQuery = new URLSearchParams(callbackUri.query); @@ -418,25 +431,19 @@ export class AzureActiveDirectoryService { } public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise { - this._logger.info(`Logging out of session '${sessionId}'`); const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); if (tokenIndex === -1) { - this._logger.info(`Session not found '${sessionId}'`); + this._logger.warn(`'${sessionId}' Session not found to remove`); return Promise.resolve(undefined); } const token = this._tokens.splice(tokenIndex, 1)[0]; - const session = await this.removeSessionByIToken(token, writeToDisk); - - if (session) { - this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); - } - - return session; + this._logger.trace(`[${token.scope}] '${sessionId}' Queued removing session`); + return this._sequencer.queue(token.scope, () => this.removeSessionByIToken(token, writeToDisk)); } public async clearSessions() { - this._logger.info('Logging out of all sessions'); + this._logger.trace('Logging out of all sessions'); this._tokens = []; await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item)); @@ -445,9 +452,11 @@ export class AzureActiveDirectoryService { }); this._refreshTimeouts.clear(); + this._logger.trace('All sessions logged out'); } private async removeSessionByIToken(token: IToken, writeToDisk: boolean = true): Promise { + this._logger.info(`[${token.scope}] '${token.sessionId}' Logging out of session`); this.removeSessionTimeout(token.sessionId); if (writeToDisk) { @@ -460,9 +469,9 @@ export class AzureActiveDirectoryService { } const session = this.convertToSessionSync(token); - this._logger.info(`Sending change event for session that was removed with scopes: ${token.scope}`); + this._logger.trace(`[${token.scope}] '${token.sessionId}' Sending change event for session that was removed`); this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); - this._logger.info(`Logged out of session '${token.sessionId}' with scopes: ${token.scope}`); + this._logger.info(`[${token.scope}] '${token.sessionId}' Logged out of session successfully!`); return session; } @@ -471,12 +480,14 @@ export class AzureActiveDirectoryService { //#region timeout private setSessionTimeout(sessionId: string, refreshToken: string, scopeData: IScopeData, timeout: number) { + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Setting refresh timeout for ${timeout} milliseconds`); this.removeSessionTimeout(sessionId); this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId); - this._logger.info('Triggering change session event...'); + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Sending change event for session that was refreshed`); this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' refresh timeout complete`); } catch (e) { if (e.message !== REFRESH_NETWORK_FAILURE) { vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); @@ -500,12 +511,13 @@ export class AzureActiveDirectoryService { private convertToTokenSync(json: ITokenResponse, scopeData: IScopeData, existingId?: string): IToken { let claims = undefined; + this._logger.trace(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse token response.`); try { if (json.id_token) { claims = JSON.parse(base64Decode(json.id_token.split('.')[1])); } else { - this._logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); + this._logger.warn(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse access_token instead since no id_token was included in the response.`); claims = JSON.parse(base64Decode(json.access_token.split('.')[1])); } } catch (e) { @@ -520,6 +532,8 @@ export class AzureActiveDirectoryService { } const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`; + const sessionId = existingId || `${id}/${randomUUID()}`; + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Token response parsed successfully.`); return { expiresIn: json.expires_in, expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined, @@ -527,7 +541,7 @@ export class AzureActiveDirectoryService { idToken: json.id_token, refreshToken: json.refresh_token, scope: scopeData.scopeStr, - sessionId: existingId || `${id}/${randomUUID()}`, + sessionId, account: { label, id, @@ -552,9 +566,7 @@ export class AzureActiveDirectoryService { private async convertToSession(token: IToken, scopeData: IScopeData): Promise { if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { - token.expiresAt - ? this._logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) - : this._logger.info('Token available from cache (for scopes ${token.scope})'); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token available from cache${token.expiresAt ? `, expires in ${token.expiresAt - Date.now()} milliseconds` : ''}.`); return { id: token.sessionId, accessToken: token.accessToken, @@ -565,7 +577,7 @@ export class AzureActiveDirectoryService { } try { - this._logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token expired or unavailable, trying refresh`); const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId); if (refreshedToken.accessToken) { return { @@ -588,18 +600,13 @@ export class AzureActiveDirectoryService { //#region refresh logic - private async refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - this._refreshingPromise = this.doRefreshToken(refreshToken, scopeData, sessionId); - try { - const result = await this._refreshingPromise; - return result; - } finally { - this._refreshingPromise = undefined; - } + private refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Queued refreshing token`); + return this._sequencer.queue(scopeData.scopeStr, () => this.doRefreshToken(refreshToken, scopeData, sessionId)); } private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - this._logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token`); const postData = new URLSearchParams({ refresh_token: refreshToken, client_id: scopeData.clientId, @@ -614,7 +621,7 @@ export class AzureActiveDirectoryService { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } this.setToken(token, scopeData); - this._logger.info(`Token refresh success for scopes: ${token.scope}`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token refresh success`); return token; } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { @@ -625,7 +632,7 @@ export class AzureActiveDirectoryService { } throw e; } - this._logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`); + this._logger.error(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token failed: ${e.message}`); throw e; } } @@ -690,6 +697,7 @@ export class AzureActiveDirectoryService { const session = await this.exchangeCodeForSession(code, verifier, scopeData); this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); + this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); resolve(session); } catch (err) { reject(err); @@ -705,6 +713,7 @@ export class AzureActiveDirectoryService { } private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { + this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with input box`); inputBox.ignoreFocusOut = true; inputBox.title = vscode.l10n.t('Microsoft Authentication'); inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.'); @@ -716,7 +725,9 @@ export class AzureActiveDirectoryService { if (code) { inputBox.dispose(); const session = await this.exchangeCodeForSession(code, verifier, scopeData); + this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' sending session changed event because session was added.`); this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); + this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); resolve(session); } }); @@ -730,7 +741,7 @@ export class AzureActiveDirectoryService { } private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { - this._logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] Exchanging login code for session`); let token: IToken | undefined; try { const postData = new URLSearchParams({ @@ -743,10 +754,10 @@ export class AzureActiveDirectoryService { }).toString(); const json = await this.fetchTokenResponse(postData, scopeData); - this._logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); + this._logger.trace(`[${scopeData.scopeStr}] Exchanging code for token succeeded!`); token = this.convertToTokenSync(json, scopeData); } catch (e) { - this._logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); + this._logger.error(`[${scopeData.scopeStr}] Error exchanging code for token: ${e}`); throw e; } @@ -754,7 +765,7 @@ export class AzureActiveDirectoryService { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } this.setToken(token, scopeData); - this._logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Exchanging login code for session succeeded!`); return await this.convertToSession(token, scopeData); } @@ -789,7 +800,7 @@ export class AzureActiveDirectoryService { if (!result || result.status > 499) { if (attempts > 3) { - this._logger.error(`Fetching token failed for scopes (${scopeData.scopeStr}): ${result ? await result.text() : errorMessage}`); + this._logger.error(`[${scopeData.scopeStr}] Fetching token failed: ${result ? await result.text() : errorMessage}`); break; } // Exponential backoff @@ -813,7 +824,7 @@ export class AzureActiveDirectoryService { //#region storage operations private setToken(token: IToken, scopeData: IScopeData): void { - this._logger.info(`Setting token for scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Setting token`); const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); if (existingTokenIndex > -1) { @@ -823,41 +834,18 @@ export class AzureActiveDirectoryService { } // Don't await because setting the token is only useful for any new windows that open. - this.storeToken(token, scopeData); + void this.storeToken(token, scopeData); } private async storeToken(token: IToken, scopeData: IScopeData): Promise { if (!vscode.window.state.focused) { - const shouldStore = await new Promise((resolve, _) => { - // To handle the case where the window is not focused for a long time. We want to store the token - // at some point so that the next time they _do_ interact with VS Code, they don't have to sign in again. - const timer = setTimeout( - () => resolve(true), - // 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time - (18000000) + Math.floor(Math.random() * 30000) - ); - const dispose = vscode.Disposable.from( - vscode.window.onDidChangeWindowState(e => { - if (e.focused) { - resolve(true); - dispose.dispose(); - clearTimeout(timer); - } - }), - this._tokenStorage.onDidChangeInOtherWindow(e => { - if (e.updated.includes(token.sessionId)) { - resolve(false); - dispose.dispose(); - clearTimeout(timer); - } - }) - ); - }); - - if (!shouldStore) { - this._logger.info(`Not storing token for scopes ${scopeData.scopeStr} because it was added in another window`); - return; + if (this._pendingTokensToStore.has(token.sessionId)) { + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, replacing token to be stored`); + } else { + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, pending storage of token`); } + this._pendingTokensToStore.set(token.sessionId, token); + return; } await this._tokenStorage.store(token.sessionId, { @@ -867,7 +855,31 @@ export class AzureActiveDirectoryService { account: token.account, endpoint: this._env.activeDirectoryEndpointUrl, }); - this._logger.info(`Stored token for scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Stored token`); + } + + private async storePendingTokens(): Promise { + if (this._pendingTokensToStore.size === 0) { + this._logger.trace('No pending tokens to store'); + return; + } + + const tokens = [...this._pendingTokensToStore.values()]; + this._pendingTokensToStore.clear(); + + this._logger.trace(`Storing ${tokens.length} pending tokens...`); + await Promise.allSettled(tokens.map(async token => { + this._logger.trace(`[${token.scope}] '${token.sessionId}' Storing pending token`); + await this._tokenStorage.store(token.sessionId, { + id: token.sessionId, + refreshToken: token.refreshToken, + scope: token.scope, + account: token.account, + endpoint: this._env.activeDirectoryEndpointUrl, + }); + this._logger.trace(`[${token.scope}] '${token.sessionId}' Stored pending token`); + })); + this._logger.trace('Done storing pending tokens'); } private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise { @@ -875,7 +887,7 @@ export class AzureActiveDirectoryService { const session = await this._tokenStorage.get(key); if (!session) { this._logger.error('session not found that was apparently just added'); - return; + continue; } if (!this.sessionMatchesEndpoint(session)) { @@ -895,36 +907,46 @@ export class AzureActiveDirectoryService { clientId: this.getClientId(scopes), tenant: this.getTenantId(scopes), }; - this._logger.info(`Session added in another window with scopes: ${session.scope}`); + this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Session added in another window`); const token = await this.refreshToken(session.refreshToken, scopeData, session.id); - this._logger.info(`Sending change event for session that was added with scopes: ${scopeData.scopeStr}`); + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Sending change event for session that was added`); this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] }); - return; + this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Session added in another window added here`); + continue; } catch (e) { // Network failures will automatically retry on next poll. if (e.message !== REFRESH_NETWORK_FAILURE) { vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); await this.removeSessionById(session.id); } - return; + continue; } } } for (const { value } of e.removed) { + this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window`); if (!this.sessionMatchesEndpoint(value)) { // If the session wasn't made for this login endpoint, ignore this update + this._logger.trace(`[${value.scope}] '${value.id}' Session doesn't match endpoint. Skipping...`); continue; } - this._logger.info(`Session removed in another window with scopes: ${value.scope}`); await this.removeSessionById(value.id, false); + this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window removed here`); } // NOTE: We don't need to handle changed sessions because all that really would give us is a new refresh token // because access tokens are not stored in Secret Storage due to their short lifespan. This new refresh token // is not useful in this window because we really only care about the lifetime of the _access_ token which we // are already managing (see usages of `setSessionTimeout`). + // However, in order to minimize the amount of times we store tokens, if a token was stored via another window, + // we cancel any pending token storage operations. + for (const sessionId of e.updated) { + if (this._pendingTokensToStore.delete(sessionId)) { + this._logger.trace(`'${sessionId}' Cancelled pending token storage because token was updated in another window`); + } + } } private sessionMatchesEndpoint(session: IStoredSession): boolean { diff --git a/extensions/microsoft-authentication/src/betterSecretStorage.ts b/extensions/microsoft-authentication/src/betterSecretStorage.ts index 14c885a7022..3cc854064b5 100644 --- a/extensions/microsoft-authentication/src/betterSecretStorage.ts +++ b/extensions/microsoft-authentication/src/betterSecretStorage.ts @@ -240,11 +240,8 @@ export class BetterTokenStorage { }, err => { Logger.error(err); - resolve(tokens); - }).then(resolve, err => { - Logger.error(err); - resolve(tokens); - }); + return tokens; + }).then(resolve); }); this._operationInProgress = false; } diff --git a/extensions/microsoft-authentication/src/common/async.ts b/extensions/microsoft-authentication/src/common/async.ts new file mode 100644 index 00000000000..527b5bbb399 --- /dev/null +++ b/extensions/microsoft-authentication/src/common/async.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 { Disposable } from 'vscode'; + +export class SequencerByKey { + + private promiseMap = new Map>(); + + queue(key: TKey, promiseTask: () => Promise): Promise { + const runningPromise = this.promiseMap.get(key) ?? Promise.resolve(); + const newPromise = runningPromise + .catch(() => { }) + .then(promiseTask) + .finally(() => { + if (this.promiseMap.get(key) === newPromise) { + this.promiseMap.delete(key); + } + }); + this.promiseMap.set(key, newPromise); + return newPromise; + } +} + +export class IntervalTimer extends Disposable { + + private _token: any; + + constructor() { + super(() => this.cancel()); + this._token = -1; + } + + cancel(): void { + if (this._token !== -1) { + clearInterval(this._token); + this._token = -1; + } + } + + cancelAndSet(runner: () => void, interval: number): void { + this.cancel(); + this._token = setInterval(() => { + runner(); + }, interval); + } +} diff --git a/extensions/microsoft-authentication/src/utils.ts b/extensions/microsoft-authentication/src/common/uri.ts similarity index 100% rename from extensions/microsoft-authentication/src/utils.ts rename to extensions/microsoft-authentication/src/common/uri.ts diff --git a/extensions/microsoft-authentication/src/extension.ts b/extensions/microsoft-authentication/src/extension.ts index 767c0a17963..02cfb4643f4 100644 --- a/extensions/microsoft-authentication/src/extension.ts +++ b/extensions/microsoft-authentication/src/extension.ts @@ -72,7 +72,7 @@ async function initMicrosoftSovereignCloudAuthProvider(context: vscode.Extension scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), }); - return await aadService.createSession(scopes.sort()); + return await aadService.createSession(scopes); } catch (e) { /* __GDPR__ "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } @@ -138,7 +138,7 @@ export async function activate(context: vscode.ExtensionContext) { scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), }); - return await loginService.createSession(scopes.sort()); + return await loginService.createSession(scopes); } catch (e) { /* __GDPR__ "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index c5b4a631afc..6ee655c509c 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -228,7 +228,9 @@ function onKeypressHandler(e: KeyboardEvent) { if (e.ctrlKey || e.shiftKey) { return; } - if (e.code === 'ArrowDown' || e.code === 'End' || e.code === 'ArrowUp' || e.code === 'Home') { + if (e.code === 'ArrowDown' || e.code === 'ArrowUp' || + e.code === 'End' || e.code === 'Home' || + e.code === 'PageUp' || e.code === 'PageDown') { // These should change the scroll position, not adjust the selected cell in the notebook e.stopPropagation(); } diff --git a/extensions/notebook-renderers/src/linkify.ts b/extensions/notebook-renderers/src/linkify.ts index ec7415a44b9..49fdc4edf8c 100644 --- a/extensions/notebook-renderers/src/linkify.ts +++ b/extensions/notebook-renderers/src/linkify.ts @@ -27,9 +27,21 @@ type LinkPart = { }; export class LinkDetector { - constructor( - ) { - // noop + + // used by unit tests + static injectedHtmlCreator: (value: string) => string; + + private shouldGenerateHtml(trustHtml: boolean) { + return trustHtml && (!!LinkDetector.injectedHtmlCreator || !!ttPolicy); + } + + private createHtml(value: string) { + if (LinkDetector.injectedHtmlCreator) { + return LinkDetector.injectedHtmlCreator(value); + } + else { + return ttPolicy?.createHTML(value).toString(); + } } /** @@ -71,9 +83,9 @@ export class LinkDetector { container.appendChild(this.createWebLink(part.value)); break; case 'html': - if (ttPolicy && trustHtml) { + if (this.shouldGenerateHtml(!!trustHtml)) { const span = document.createElement('span'); - span.innerHTML = ttPolicy.createHTML(part.value).toString(); + span.innerHTML = this.createHtml(part.value)!; container.appendChild(span); } else { container.appendChild(document.createTextNode(part.value)); @@ -142,8 +154,8 @@ export class LinkDetector { return [{ kind: 'text', value: text, captures: [] }]; } - const regexes: RegExp[] = [WEB_LINK_REGEX, PATH_LINK_REGEX, HTML_LINK_REGEX]; - const kinds: LinkKind[] = ['web', 'path', 'html']; + const regexes: RegExp[] = [HTML_LINK_REGEX, WEB_LINK_REGEX, PATH_LINK_REGEX]; + const kinds: LinkKind[] = ['html', 'web', 'path']; const result: LinkPart[] = []; const splitOne = (text: string, regexIndex: number) => { diff --git a/extensions/notebook-renderers/src/test/linkify.test.ts b/extensions/notebook-renderers/src/test/linkify.test.ts new file mode 100644 index 00000000000..7ee5487a84b --- /dev/null +++ b/extensions/notebook-renderers/src/test/linkify.test.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { JSDOM } from "jsdom"; +import { LinkDetector, linkify } from '../linkify'; + +const dom = new JSDOM(); +global.document = dom.window.document; + +suite('Notebook builtin output link detection', () => { + + LinkDetector.injectedHtmlCreator = (value: string) => value; + + test('no links', () => { + const htmlWithLinks = linkify('hello', true, undefined, true); + assert.equal(htmlWithLinks.innerHTML, 'hello'); + }); + + test('web link detection', () => { + const htmlWithLinks = linkify('something www.example.com something', true, undefined, true); + + assert.equal(htmlWithLinks.innerHTML, 'something www.example.com something'); + assert.equal(htmlWithLinks.textContent, 'something www.example.com something'); + }); + + test('html link detection', () => { + const htmlWithLinks = linkify('something link something', true, undefined, true); + + assert.equal(htmlWithLinks.innerHTML, 'something link something'); + assert.equal(htmlWithLinks.textContent, 'something link something'); + }); + + test('html link without trust', () => { + const trustHtml = false; + const htmlWithLinks = linkify('something link something', true, undefined, trustHtml); + + assert.equal(htmlWithLinks.innerHTML, 'something <a href="file.py">link</a> something'); + assert.equal(htmlWithLinks.textContent, 'something link something'); + }); +}); + diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index 0f747900377..7c92a5a01f4 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -8,6 +8,7 @@ import { activate } from '..'; import { RendererApi } from 'vscode-notebook-renderer'; import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes'; import { JSDOM } from "jsdom"; +import { LinkDetector } from '../linkify'; const dom = new JSDOM(); global.document = dom.window.document; @@ -15,13 +16,12 @@ global.document = dom.window.document; suite('Notebook builtin output renderer', () => { const error = { - name: "NameError", - message: "name 'x' is not defined", + name: "TypeError", + message: "Expected type `str`, but received type ``", stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + - "\n\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)" + - "\nCell \u001b[1;32mIn[3], line 1\u001b[0m" + - "\n\u001b[1;32m----> 1\u001b[0m \u001b[39mprint\u001b[39m(x)" + - "\n\n\u001b[1;31mNameError\u001b[0m: name 'x' is not defined" + "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)" + + "\u001b[1;32mc:\\src\\test\\ws1\\testing.py\u001b[0m in \u001b[0;36mline 2\n\u001b[0;32m 35\u001b[0m \u001b[39m# %%\u001b[39;00m\n\u001b[1;32m----> 36\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mTypeError\u001b[39;00m(\u001b[39m'\u001b[39m\u001b[39merror = f\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mExpected type `str`, but received type `\u001b[39m\u001b[39m{\u001b[39m\u001b[39mtype(name)}`\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m'\u001b[39m)\n" + + "\u001b[1;31mTypeError\u001b[0m: Expected type `str`, but received type ``\"" }; const errorMimeType = 'application/vnd.code.notebook.error'; @@ -233,7 +233,7 @@ suite('Notebook builtin output renderer', () => { assert.ok(firstOutputElement.innerHTML.indexOf('>first stream content') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); assert.ok(firstOutputElement.innerHTML.indexOf('appended1') > -1, `Content was not appended to output element: ${outputHtml.cellElement.innerHTML}`); - assert.ok(secondOutputElement.innerHTML.indexOf('>NameError -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); + assert.ok(secondOutputElement.innerHTML.indexOf('>TypeError -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); assert.ok(thirdOutputElement.innerHTML.indexOf('>second stream content') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); assert.ok(thirdOutputElement.innerHTML.indexOf('appended3') > -1, `Content was not appended to output element: ${outputHtml.cellElement.innerHTML}`); }); @@ -274,6 +274,7 @@ suite('Notebook builtin output renderer', () => { }); test(`Render with wordwrap and scrolling for error output`, async () => { + LinkDetector.injectedHtmlCreator = (value: string) => value; const context = createContext({ outputWordWrap: true, outputScrolling: true }); const renderer = await activate(context); assert.ok(renderer, 'Renderer not created'); @@ -287,7 +288,9 @@ suite('Notebook builtin output renderer', () => { assert.ok(outputElement.classList.contains('remove-padding'), 'Padding should be removed for scrollable outputs'); assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'), `output content classList should contain word-wrap and scrollable ${inserted.classList}`); - assert.ok(inserted.innerHTML.indexOf('>: name \'x\' is not defined -1, `Content was not added to output element:\n ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>Expected type `str`, but received type') > -1, `Content was not added to output element:\n ${outputElement.innerHTML}`); + assert.ok(inserted.textContent!.indexOf('Expected type `str`, but received type ``') > -1, `Content was not added to output element:\n ${outputElement.textContent}`); + assert.ok(inserted.textContent!.indexOf(' { @@ -303,7 +306,7 @@ suite('Notebook builtin output renderer', () => { await renderer!.renderOutputItem(outputItem2, outputElement); const inserted = outputElement.firstChild as HTMLElement; - assert.ok(inserted.innerHTML.indexOf('>: name \'x\' is not definedreplaced content { await renderer!.renderOutputItem(outputItem3, thirdOutputElement); assert.ok(firstOutputElement.innerHTML.indexOf('>first stream content -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); - assert.ok(secondOutputElement.innerHTML.indexOf('>NameError -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); + assert.ok(secondOutputElement.innerHTML.indexOf('>TypeError -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); assert.ok(thirdOutputElement.innerHTML.indexOf('>second stream content -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`); }); diff --git a/extensions/notebook-renderers/yarn.lock b/extensions/notebook-renderers/yarn.lock index fac17bd8f60..3cbe531e0fd 100644 --- a/extensions/notebook-renderers/yarn.lock +++ b/extensions/notebook-renderers/yarn.lock @@ -403,9 +403,9 @@ whatwg-url@^12.0.0, whatwg-url@^12.0.1: webidl-conversions "^7.0.0" word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.4" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" + integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== ws@^8.13.0: version "8.13.0" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index e3dbfcfe26a..2bacd70b76d 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -16,25 +16,22 @@ "terminalQuickFixProvider" ], "scripts": { - "compile": "gulp compile-extension:npm", - "watch": "gulp watch-extension:npm" + "compile": "npx gulp compile-extension:npm", + "watch": "npx gulp watch-extension:npm" }, "dependencies": { - "find-up": "^4.1.0", + "find-up": "^5.0.0", "find-yarn-workspace-root": "^2.0.0", - "jsonc-parser": "^2.2.1", - "minimatch": "^3.0.4", + "jsonc-parser": "^3.2.0", + "minimatch": "^5.1.6", "request-light": "^0.7.0", - "which": "^2.0.2", - "which-pm": "^2.0.0" + "which": "^4.0.0", + "which-pm": "^2.1.1" }, "devDependencies": { - "@types/minimatch": "^3.0.3", + "@types/minimatch": "^5.1.2", "@types/node": "18.x", - "@types/which": "^2.0.0" - }, - "resolutions": { - "which-pm/load-yaml-file/**/argparse": "1.0.9" + "@types/which": "^3.0.0" }, "main": "./out/npmMain", "browser": "./dist/browser/npmBrowserMain", diff --git a/extensions/npm/src/features/packageJSONContribution.ts b/extensions/npm/src/features/packageJSONContribution.ts index 5a9250d97de..f2318371673 100644 --- a/extensions/npm/src/features/packageJSONContribution.ts +++ b/extensions/npm/src/features/packageJSONContribution.ts @@ -252,11 +252,12 @@ export class PackageJSONContribution implements IJSONContribution { } private isValidNPMName(name: string): boolean { - // following rules from https://github.com/npm/validate-npm-package-name - if (!name || name.length > 214 || name.match(/^[_.]/)) { + // following rules from https://github.com/npm/validate-npm-package-name, + // leading slash added as additional security measure + if (!name || name.length > 214 || name.match(/^[-_.\s]/)) { return false; } - const match = name.match(/^(?:@([^/]+?)[/])?([^/]+?)$/); + const match = name.match(/^(?:@([^/~\s)('!*]+?)[/])?([^/~)('!*\s]+?)$/); if (match) { const scope = match[1]; if (scope && encodeURIComponent(scope) !== scope) { @@ -284,7 +285,7 @@ export class PackageJSONContribution implements IJSONContribution { private npmView(npmCommandPath: string, pack: string, resource: Uri | undefined): Promise { return new Promise((resolve, _reject) => { - const args = ['view', '--json', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time']; + const args = ['view', '--json', '--', pack, 'description', 'dist-tags.latest', 'homepage', 'version', 'time']; const cwd = resource && resource.scheme === 'file' ? dirname(resource.fsPath) : undefined; cp.execFile(npmCommandPath, args, { cwd }, (error, stdout) => { if (!error) { diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index a066aac1e96..d03a72a72fc 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -97,7 +97,7 @@ export async function activate(context: vscode.ExtensionContext): Promise } async function getNPMCommandPath(): Promise { - if (canRunNpmInCurrentWorkspace()) { + if (vscode.workspace.isTrusted && canRunNpmInCurrentWorkspace()) { try { return await which(process.platform === 'win32' ? 'npm.cmd' : 'npm'); } catch (e) { diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index cd465e173e6..12f7036d0f6 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -2,22 +2,22 @@ # yarn lockfile v1 -"@types/minimatch@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimatch@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@18.x": version "18.15.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== -"@types/which@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.0.tgz#446d35586611dee657120de8e0457382a658fc25" - integrity sha512-JHTNOEpZnACQdsTojWggn+SQ8IucfqEhtz7g8Z0G67WdSj4x3F0X5I2c/CVcl8z/QukGrIHeQ/N49v1au74XFQ== +"@types/which@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/which/-/which-3.0.0.tgz#849afdd9fdcb0b67339b9cfc80fa6ea4e0253fc5" + integrity sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ== -argparse@1.0.9, argparse@^1.0.7: +argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY= @@ -29,13 +29,12 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -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== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" - concat-map "0.0.1" braces@^3.0.1: version "3.0.2" @@ -44,11 +43,6 @@ braces@^3.0.1: dependencies: fill-range "^7.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 sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -61,12 +55,12 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: - locate-path "^5.0.0" + locate-path "^6.0.0" path-exists "^4.0.0" find-yarn-workspace-root@^2.0.0: @@ -86,10 +80,10 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== js-yaml@^3.13.0: version "3.14.0" @@ -99,10 +93,10 @@ js-yaml@^3.13.0: argparse "^1.0.7" esprima "^4.0.0" -jsonc-parser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== load-yaml-file@^0.2.0: version "0.2.0" @@ -114,12 +108,12 @@ load-yaml-file@^0.2.0: pify "^4.0.1" strip-bom "^3.0.0" -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: - p-locate "^4.1.0" + p-locate "^5.0.0" micromatch@^4.0.2: version "4.0.2" @@ -129,31 +123,26 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -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== +minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - p-try "^2.0.0" + yocto-queue "^0.1.0" -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + p-limit "^3.0.2" path-exists@^4.0.0: version "4.0.0" @@ -192,17 +181,22 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -which-pm@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.0.0.tgz#8245609ecfe64bf751d0eef2f376d83bf1ddb7ae" - integrity sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w== +which-pm@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-2.1.1.tgz#0be2b70c67e94a32e87b9768a94a7f0954f2dcfa" + integrity sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ== dependencies: load-yaml-file "^0.2.0" path-exists "^4.0.0" -which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== +which@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" + integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== dependencies: - isexe "^2.0.0" + isexe "^3.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/extensions/perl/package.json b/extensions/perl/package.json index 3357e1ff3bd..003ec922c32 100644 --- a/extensions/perl/package.json +++ b/extensions/perl/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/perl.tmbundle Syntaxes/Perl.plist ./syntaxes/perl.tmLanguage.json Syntaxes/Perl%%206.tmLanguage ./syntaxes/perl6.tmLanguage.json" + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/perl.tmbundle Syntaxes/Perl.plist ./syntaxes/perl.tmLanguage.json Syntaxes/Perl%206.tmLanguage ./syntaxes/perl6.tmLanguage.json" }, "contributes": { "languages": [ @@ -31,18 +31,23 @@ "configuration": "./perl.language-configuration.json" }, { - "id": "perl6", + "id": "raku", "aliases": [ - "Perl 6", + "Raku", + "Perl6", "perl6" ], "extensions": [ + ".raku", + ".rakumod", + ".rakutest", + ".rakudoc", + ".nqp", ".p6", ".pl6", - ".pm6", - ".nqp" + ".pm6" ], - "firstLine": "(^#!.*\\bperl6\\b)|use\\s+v6", + "firstLine": "(^#!.*\\bperl6\\b)|use\\s+v6|raku|=begin\\spod|my\\sclass", "configuration": "./perl6.language-configuration.json" } ], @@ -56,7 +61,7 @@ ] }, { - "language": "perl6", + "language": "raku", "scopeName": "source.perl.6", "path": "./syntaxes/perl6.tmLanguage.json" } diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index e81c3b9adca..f0e4d4bb352 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -278,18 +278,14 @@ } ], "colors": { - "editor.background": "#000c18", "editor.foreground": "#6688cc", - // Base // "foreground": "", "focusBorder": "#596F99", // "contrastActiveBorder": "", // "contrastBorder": "", - // "widget.shadow": "", - "input.background": "#181f2f", // "input.border": "", // "input.foreground": "", @@ -300,17 +296,13 @@ "inputValidation.warningBorder": "#5B7E7A", "inputValidation.errorBackground": "#A22D44", "inputValidation.errorBorder": "#AB395B", - "badge.background": "#0063a5", "progressBar.background": "#0063a5", - "dropdown.background": "#181f2f", // "dropdown.foreground": "", // "dropdown.border": "", - "button.background": "#2B3C5D", // "button.foreground": "", - "list.activeSelectionBackground": "#08286b", // "list.activeSelectionForeground": "", "quickInputList.focusBackground": "#08286b", @@ -318,12 +310,10 @@ "list.inactiveSelectionBackground": "#152037", "list.dropBackground": "#041D52", "list.highlightForeground": "#0063a5", - "scrollbar.shadow": "#515E91AA", "scrollbarSlider.activeBackground": "#3B3F5188", "scrollbarSlider.background": "#1F2230AA", "scrollbarSlider.hoverBackground": "#3B3F5188", - // Editor "editorWidget.background": "#262641", "editorCursor.foreground": "#ddbb88", @@ -350,14 +340,12 @@ // "editor.selectionHighlightBackground": "", // "editor.wordHighlightBackground": "", // "editor.wordHighlightStrongBackground": "", - // Editor: Suggest Widget // "editorSuggestWidget.background": "", // "editorSuggestWidget.border": "", // "editorSuggestWidget.foreground": "", // "editorSuggestWidget.highlightForeground": "", // "editorSuggestWidget.selectedBackground": "", - // Editor: Peek View "peekViewResult.background": "#060621", // "peekViewResult.lineForeground": "", @@ -371,7 +359,6 @@ "peekViewResult.matchHighlightBackground": "#eeeeee44", // "peekViewTitleLabel.foreground": "", // "peekViewTitleDescription.foreground": "", - // Ports "ports.iconRunningProcessForeground": "#80a2c2", // Editor: Diff @@ -379,23 +366,18 @@ // "diffEditor.insertedTextBorder": "", "diffEditor.removedTextBackground": "#892F4688", // "diffEditor.removedTextBorder": "", - - // Editor: Minimap "minimap.selectionHighlight": "#750000", - // Workbench: Title "titleBar.activeBackground": "#10192c", // "titleBar.activeForeground": "", // "titleBar.inactiveBackground": "", // "titleBar.inactiveForeground": "", - // Workbench: Editors // "editorGroupHeader.noTabsBackground": "", "editorGroup.border": "#2b2b4a", "editorGroup.dropBackground": "#25375daa", "editorGroupHeader.tabsBackground": "#1c1c2a", - // Workbench: Tabs "tab.border": "#2b2b4a", // "tab.activeBackground": "", @@ -403,26 +385,21 @@ // "tab.activeForeground": "", // "tab.inactiveForeground": "", "tab.lastPinnedBorder": "#2b3c5d", - // Workbench: Activity Bar "activityBar.background": "#051336", // "activityBar.foreground": "", // "activityBarBadge.background": "", // "activityBarBadge.foreground": "", - "activityBarItem.profilesBackground": "#082877", - // Workbench: Panel // "panel.background": "", "panel.border": "#2b2b4a", // "panelTitle.activeBorder": "", // "panelTitle.activeForeground": "", // "panelTitle.inactiveForeground": "", - // Workbench: Side Bar "sideBar.background": "#060621", // "sideBarTitle.foreground": "", "sideBarSectionHeader.background": "#10192c", - // Workbench: Status Bar "statusBar.background": "#10192c", "statusBar.noFolderBackground": "#10192c", @@ -433,20 +410,16 @@ "statusBarItem.prominentHoverBackground": "#0063a5dd", // "statusBarItem.activeBackground": "", // "statusBarItem.hoverBackground": "", - // Workbench: Debug "debugToolBar.background": "#051336", "debugExceptionWidget.background": "#051336", "debugExceptionWidget.border": "#AB395B", - // Workbench: Quick Open "pickerGroup.border": "#596F99", "pickerGroup.foreground": "#596F99", - // Workbench: Extensions "extensionButton.prominentBackground": "#5f8b3b", "extensionButton.prominentHoverBackground": "#5f8b3bbb", - // Workbench: Terminal "terminal.ansiBlack": "#111111", "terminal.ansiRed": "#ff9da4", diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index ed80b1785f6..5565e2dbec6 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -77,6 +77,7 @@ "source.cpp keyword.operator.new", "keyword.operator.delete", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator", "entity.name.operator" ], diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 816fbf9395a..7b671065646 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -406,6 +406,7 @@ "source.cpp keyword.operator.new", "source.cpp keyword.operator.delete", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator" ], "settings": { diff --git a/extensions/theme-defaults/themes/hc_light.json b/extensions/theme-defaults/themes/hc_light.json index 17c1af9ef34..aaf0e2f9cf3 100644 --- a/extensions/theme-defaults/themes/hc_light.json +++ b/extensions/theme-defaults/themes/hc_light.json @@ -442,6 +442,7 @@ "source.cpp keyword.operator.new", "source.cpp keyword.operator.delete", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator", "entity.name.operator" ], diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index f73b79579f0..7f77883f3ee 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -77,6 +77,7 @@ "source.cpp keyword.operator.new", "source.cpp keyword.operator.delete", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator", "entity.name.operator" ], diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 3554c486209..ec50a96586a 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -32,7 +32,6 @@ "ports.iconRunningProcessForeground": "#369432", "activityBar.background": "#221a0f", "activityBar.foreground": "#d3af86", - "activityBarItem.profilesBackground": "#47351d", "sideBar.background": "#362712", "menu.background": "#362712", "menu.foreground": "#CCCCCC", @@ -124,6 +123,7 @@ "keyword.operator.new.cpp", "keyword.operator.delete.cpp", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator" ], "settings": { diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 691680512a4..00ba8b3ace6 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -269,6 +269,7 @@ "keyword.operator.new.cpp", "keyword.operator.delete.cpp", "keyword.other.using", + "keyword.other.directive.using", "keyword.other.operator" ], "settings": { diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json index b0bdf8e90a9..840a1764bf1 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json @@ -36,7 +36,6 @@ "statusBar.noFolderBackground": "#001126", "statusBar.debuggingBackground": "#001126", "activityBar.background": "#001733", - "activityBarItem.profilesBackground": "#003271", "progressBar.background": "#bbdaffcc", "badge.background": "#bbdaffcc", "badge.foreground": "#001733", diff --git a/extensions/tunnel-forwarding/src/extension.ts b/extensions/tunnel-forwarding/src/extension.ts index f6ef85e71b1..951fc6f5b8d 100644 --- a/extensions/tunnel-forwarding/src/extension.ts +++ b/extensions/tunnel-forwarding/src/extension.ts @@ -67,7 +67,7 @@ export async function activate(context: vscode.ExtensionContext) { } const logger = new Logger(vscode.l10n.t('Port Forwarding')); - const provider = new TunnelProvider(logger); + const provider = new TunnelProvider(logger, context); context.subscriptions.push( vscode.commands.registerCommand('tunnel-forwarding.showLog', () => logger.show()), @@ -120,6 +120,8 @@ class Logger { } } +const didWarnPublicKey = 'didWarnPublic'; + class TunnelProvider implements vscode.TunnelProvider { private readonly tunnels = new Set(); private readonly stateChange = new vscode.EventEmitter(); @@ -136,10 +138,16 @@ class TunnelProvider implements vscode.TunnelProvider { public readonly onDidStateChange = this.stateChange.event; - constructor(private readonly logger: Logger) { } + constructor(private readonly logger: Logger, private readonly context: vscode.ExtensionContext) { } /** @inheritdoc */ - public async provideTunnel(tunnelOptions: vscode.TunnelOptions): Promise { + public async provideTunnel(tunnelOptions: vscode.TunnelOptions): Promise { + if (tunnelOptions.privacy === TunnelPrivacyId.Public) { + if (!(await this.consentPublicPort(tunnelOptions.remoteAddress.port))) { + return; + } + } + const tunnel = new Tunnel( tunnelOptions.remoteAddress, (tunnelOptions.privacy as TunnelPrivacyId) || TunnelPrivacyId.Private, @@ -184,6 +192,31 @@ class TunnelProvider implements vscode.TunnelProvider { this.updateActivePortsIfRunning(); } + private async consentPublicPort(portNumber: number) { + const didWarn = this.context.globalState.get(didWarnPublicKey, false); + if (didWarn) { + return true; + } + + const continueOpt = vscode.l10n.t('Continue'); + const dontShowAgain = vscode.l10n.t("Don't show again"); + const r = await vscode.window.showWarningMessage( + vscode.l10n.t("You're about to create a publicly forwarded port. Anyone on the internet will be able to connect to the service listening on port {0}. You should only proceed if this service is secure and non-sensitive.", portNumber), + { modal: true }, + continueOpt, + dontShowAgain, + ); + if (r === continueOpt) { + // continue + } else if (r === dontShowAgain) { + await this.context.globalState.update(didWarnPublicKey, true); + } else { + return false; + } + + return true; + } + private isInStateWithProcess(process: ChildProcessWithoutNullStreams) { return ( (this.state.state === State.Starting || this.state.state === State.Active) && diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index eaa40fcffde..23a23f02878 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1322,6 +1322,12 @@ "default": false, "description": "%configuration.preferGoToSourceDefinition%", "scope": "window" + }, + "typescript.workspaceSymbols.excludeLibrarySymbols": { + "type": "boolean", + "default": true, + "markdownDescription": "%typescript.workspaceSymbols.excludeLibrarySymbols%", + "scope": "window" } } }, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 771f863a4e9..d3adaf7678d 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -153,6 +153,7 @@ "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. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics. Requires using TypeScript 4.8 or newer in the workspace.", + "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in `Go To Symbol in Workspace` results. Requires using TypeScript 5.3+ 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/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index 0d60cd74932..5fce20d1d1f 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -122,6 +122,7 @@ export interface TypeScriptServiceConfiguration { readonly enableTsServerTracing: boolean; readonly localNodePath: string | null; readonly globalNodePath: string | null; + readonly workspaceSymbolsExcludeLibrarySymbols: boolean; } export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean { @@ -158,6 +159,7 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu enableTsServerTracing: this.readEnableTsServerTracing(configuration), localNodePath: this.readLocalNodePath(configuration), globalNodePath: this.readGlobalNodePath(configuration), + workspaceSymbolsExcludeLibrarySymbols: this.readWorkspaceSymbolsExcludeLibrarySymbols(configuration), }; } @@ -255,4 +257,8 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu private readWebProjectWideIntellisenseSuppressSemanticErrors(configuration: vscode.WorkspaceConfiguration): boolean { return configuration.get('typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors', true); } + + private readWorkspaceSymbolsExcludeLibrarySymbols(configuration: vscode.WorkspaceConfiguration): boolean { + return configuration.get('typescript.workspaceSymbols.excludeLibrarySymbols', true); + } } diff --git a/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts b/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts index 0d4da5b39a5..91ca9612e70 100644 --- a/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/languageFeatures/updatePathsOnRename.ts @@ -56,38 +56,39 @@ class UpdateImportsOnFileRenameHandler extends Disposable { super(); this._register(vscode.workspace.onDidRenameFiles(async (e) => { - const [{ newUri, oldUri }] = e.files; - const newFilePath = this.client.toTsFilePath(newUri); - if (!newFilePath) { - return; + for (const { newUri, oldUri } of e.files) { + const newFilePath = this.client.toTsFilePath(newUri); + if (!newFilePath) { + continue; + } + + const oldFilePath = this.client.toTsFilePath(oldUri); + if (!oldFilePath) { + continue; + } + + const config = this.getConfiguration(newUri); + const setting = config.get(updateImportsOnFileMoveName); + if (setting === UpdateImportsOnFileMoveSetting.Never) { + continue; + } + + // Try to get a js/ts file that is being moved + // For directory moves, this returns a js/ts file under the directory. + const jsTsFileThatIsBeingMoved = await this.getJsTsFileBeingMoved(newUri); + if (!jsTsFileThatIsBeingMoved || !this.client.toTsFilePath(jsTsFileThatIsBeingMoved)) { + continue; + } + + this._pendingRenames.add({ oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved }); + + this._delayer.trigger(() => { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: vscode.l10n.t("Checking for update of JS/TS imports") + }, () => this.flushRenames()); + }); } - - const oldFilePath = this.client.toTsFilePath(oldUri); - if (!oldFilePath) { - return; - } - - const config = this.getConfiguration(newUri); - const setting = config.get(updateImportsOnFileMoveName); - if (setting === UpdateImportsOnFileMoveSetting.Never) { - return; - } - - // Try to get a js/ts file that is being moved - // For directory moves, this returns a js/ts file under the directory. - const jsTsFileThatIsBeingMoved = await this.getJsTsFileBeingMoved(newUri); - if (!jsTsFileThatIsBeingMoved || !this.client.toTsFilePath(jsTsFileThatIsBeingMoved)) { - return; - } - - this._pendingRenames.add({ oldUri, newUri, newFilePath, oldFilePath, jsTsFileThatIsBeingMoved }); - - this._delayer.trigger(() => { - vscode.window.withProgress({ - location: vscode.ProgressLocation.Window, - title: vscode.l10n.t("Checking for update of JS/TS imports") - }, () => this.flushRenames()); - }); })); } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 5b7591bfd8f..7553be7ed49 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -558,6 +558,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType providePrefixAndSuffixTextForRename: true, allowRenameOfImportPath: true, includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports, + excludeLibrarySymbolsInNavTo: this._configuration.workspaceSymbolsExcludeLibrarySymbols, }, watchOptions }; diff --git a/extensions/vb/package.json b/extensions/vb/package.json index 6e5dd080c0b..801ef7180da 100644 --- a/extensions/vb/package.json +++ b/extensions/vb/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%%20VB.net.plist ./syntaxes/asp-vb-net.tmlanguage.json" + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin textmate/asp.vb.net.tmbundle Syntaxes/ASP%20VB.net.plist ./syntaxes/asp-vb-net.tmlanguage.json" }, "contributes": { "languages": [ diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index ad2305f8a6a..4f9f2c1965d 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -45,7 +45,7 @@ "textSearchProvider", "timeline", "tokenInformation", - "treeViewActiveItem", + "treeViewActiveItem", "treeViewReveal", "workspaceTrust", "telemetry", diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 8cb8442b6f2..eb72136ccf4 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -17,7 +17,7 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" }, "dependencies": { - "jsonc-parser": "2.2.1" + "jsonc-parser": "^3.2.0" }, "devDependencies": { "@types/node": "18.x" diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.p6 b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.p6 new file mode 100644 index 00000000000..e8b55eb6f3d --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.p6 @@ -0,0 +1,27 @@ +# Example taken from https://en.wikipedia.org/wiki/Raku_(programming_language) +class Point is rw { + has $.x; + has $.y; + + method distance( Point $p ) { + sqrt(($!x - $p.x) ** 2 + ($!y - $p.y) ** 2) + } + + method distance-to-center { + self.distance: Point.new(x => 0, y => 0) + } +} + +my $point = Point.new( x => 1.2, y => -3.7 ); +say "Point's location: (", $point.x, ', ', $point.y, ')'; +# OUTPUT: Point's location: (1.2, -3.7) + +# Changing x and y (note methods "x" and "y" used as lvalues): +$point.x = 3; +$point.y = 4; +say "Point's location: (", $point.x, ', ', $point.y, ')'; +# OUTPUT: Point's location: (3, 4) + +my $other-point = Point.new(x => -5, y => 10); +$point.distance($other-point); #=> 10 +$point.distance-to-center; #=> 5 diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json b/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json index 05b234780ac..0bf691548b3 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json @@ -1,21 +1,7 @@ [ { - "c": "%", - "t": "text.bibtex comment.line.percentage.bibtex punctuation.definition.comment.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" - } - }, - { - "c": " a sample bibliography file", - "t": "text.bibtex comment.line.percentage.bibtex", + "c": "% a sample bibliography file", + "t": "text.bibtex comment.block.bibtex", "r": { "dark_plus": "comment: #6A9955", "light_plus": "comment: #008000", @@ -29,7 +15,7 @@ }, { "c": "%", - "t": "text.bibtex comment.line.percentage.bibtex punctuation.definition.comment.bibtex", + "t": "text.bibtex comment.block.bibtex", "r": { "dark_plus": "comment: #6A9955", "light_plus": "comment: #008000", @@ -1386,8 +1372,8 @@ } }, { - "c": "%", - "t": "text.bibtex comment.line.percentage.bibtex punctuation.definition.comment.bibtex", + "c": "% The authors mentioned here are almost, but not quite,", + "t": "text.bibtex comment.block.bibtex", "r": { "dark_plus": "comment: #6A9955", "light_plus": "comment: #008000", @@ -1400,36 +1386,8 @@ } }, { - "c": " The authors mentioned here are almost, but not quite,", - "t": "text.bibtex comment.line.percentage.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" - } - }, - { - "c": "%", - "t": "text.bibtex comment.line.percentage.bibtex punctuation.definition.comment.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" - } - }, - { - "c": " entirely unrelated to Matt Groening.", - "t": "text.bibtex comment.line.percentage.bibtex", + "c": "% entirely unrelated to Matt Groening.", + "t": "text.bibtex comment.block.bibtex", "r": { "dark_plus": "comment: #6A9955", "light_plus": "comment: #008000", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json index 34a6e9e8654..e846aa23b4e 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json @@ -1,16 +1,16 @@ [ { "c": "using", - "t": "source.cs keyword.other.using.cs", + "t": "source.cs keyword.other.directive.using.cs", "r": { - "dark_plus": "keyword.other.using: #C586C0", - "light_plus": "keyword.other.using: #AF00DB", + "dark_plus": "keyword.other.directive.using: #C586C0", + "light_plus": "keyword.other.directive.using: #AF00DB", "dark_vs": "keyword: #569CD6", "light_vs": "keyword: #0000FF", - "hc_black": "keyword.other.using: #C586C0", - "dark_modern": "keyword.other.using: #C586C0", - "hc_light": "keyword.other.using: #B5200D", - "light_modern": "keyword.other.using: #AF00DB" + "hc_black": "keyword.other.directive.using: #C586C0", + "dark_modern": "keyword.other.directive.using: #C586C0", + "hc_light": "keyword.other.directive.using: #B5200D", + "light_modern": "keyword.other.directive.using: #AF00DB" } }, { @@ -57,16 +57,16 @@ }, { "c": "namespace", - "t": "source.cs keyword.other.namespace.cs", + "t": "source.cs storage.type.namespace.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -127,16 +127,16 @@ }, { "c": "class", - "t": "source.cs keyword.other.class.cs", + "t": "source.cs storage.type.class.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -211,7 +211,7 @@ }, { "c": "static", - "t": "source.cs storage.modifier.cs", + "t": "source.cs storage.modifier.static.cs", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -239,7 +239,7 @@ }, { "c": "void", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.void.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -295,7 +295,7 @@ }, { "c": "string", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.string.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -421,7 +421,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -799,7 +799,7 @@ }, { "c": "const", - "t": "source.cs storage.modifier.cs", + "t": "source.cs storage.modifier.const.cs", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -827,7 +827,7 @@ }, { "c": "double", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.double.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1023,7 +1023,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1177,7 +1177,7 @@ }, { "c": "double", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.double.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1429,16 +1429,16 @@ }, { "c": " ", - "t": "source.cs punctuation.whitespace.comment.leading.cs", + "t": "source.cs comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" } }, { @@ -1751,7 +1751,7 @@ }, { "c": "public", - "t": "source.cs storage.modifier.cs", + "t": "source.cs storage.modifier.public.cs", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -1779,7 +1779,7 @@ }, { "c": "void", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.void.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1947,16 +1947,16 @@ }, { "c": "new", - "t": "source.cs keyword.other.new.cs", + "t": "source.cs keyword.operator.expression.new.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "keyword.operator.expression: #569CD6", + "light_plus": "keyword.operator.expression: #0000FF", + "dark_vs": "keyword.operator.expression: #569CD6", + "light_vs": "keyword.operator.expression: #0000FF", + "hc_black": "keyword.operator.expression: #569CD6", + "dark_modern": "keyword.operator.expression: #569CD6", + "hc_light": "keyword.operator.expression: #0F4A85", + "light_modern": "keyword.operator.expression: #0000FF" } }, { @@ -2003,7 +2003,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2115,7 +2115,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2255,16 +2255,16 @@ }, { "c": "new", - "t": "source.cs keyword.other.new.cs", + "t": "source.cs keyword.operator.expression.new.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "keyword.operator.expression: #569CD6", + "light_plus": "keyword.operator.expression: #0000FF", + "dark_vs": "keyword.operator.expression: #569CD6", + "light_vs": "keyword.operator.expression: #0000FF", + "hc_black": "keyword.operator.expression: #569CD6", + "dark_modern": "keyword.operator.expression: #569CD6", + "hc_light": "keyword.operator.expression: #0F4A85", + "light_modern": "keyword.operator.expression: #0000FF" } }, { @@ -2311,7 +2311,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2423,7 +2423,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2521,16 +2521,16 @@ }, { "c": "new", - "t": "source.cs keyword.other.new.cs", + "t": "source.cs keyword.operator.expression.new.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "keyword.operator.expression: #569CD6", + "light_plus": "keyword.operator.expression: #0000FF", + "dark_vs": "keyword.operator.expression: #569CD6", + "light_vs": "keyword.operator.expression: #0000FF", + "hc_black": "keyword.operator.expression: #569CD6", + "dark_modern": "keyword.operator.expression: #569CD6", + "hc_light": "keyword.operator.expression: #0F4A85", + "light_modern": "keyword.operator.expression: #0000FF" } }, { @@ -2577,7 +2577,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.cs", + "t": "source.cs keyword.type.int.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json index 5945550fd4e..4307f03ed1e 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json @@ -43,16 +43,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs keyword.other.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs storage.type.var.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -169,16 +169,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs keyword.other.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs storage.type.var.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -505,16 +505,16 @@ }, { "c": " ", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock punctuation.whitespace.comment.leading.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" } }, { @@ -561,16 +561,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock keyword.other.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock storage.type.var.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -757,16 +757,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock keyword.other.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock storage.type.var.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" } }, { @@ -939,16 +939,16 @@ }, { "c": " ", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock punctuation.whitespace.comment.leading.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" } }, { diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_p6.json b/extensions/vscode-colorize-tests/test/colorize-results/test_p6.json new file mode 100644 index 00000000000..c4c2106a171 --- /dev/null +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_p6.json @@ -0,0 +1,1836 @@ +[ + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": " Example taken from https://en.wikipedia.org/wiki/Raku_(programming_language)", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "class", + "t": "source.perl.6 meta.class.perl.6 storage.type.class.perl.6", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " ", + "t": "source.perl.6 meta.class.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "Point", + "t": "source.perl.6 meta.class.perl.6 entity.name.type.class.perl.6", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0", + "dark_modern": "entity.name.type: #4EC9B0", + "hc_light": "entity.name.type: #185E73", + "light_modern": "entity.name.type: #267F99" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "is", + "t": "source.perl.6 storage.modifier.type.constraints.perl", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6", + "dark_modern": "storage.modifier: #569CD6", + "hc_light": "storage.modifier: #0F4A85", + "light_modern": "storage.modifier: #0000FF" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "rw", + "t": "source.perl.6 storage.modifier.perl", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6", + "dark_modern": "storage.modifier: #569CD6", + "hc_light": "storage.modifier: #0F4A85", + "light_modern": "storage.modifier: #0000FF" + } + }, + { + "c": " {", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "has", + "t": "source.perl.6 storage.type.variable.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " $.", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": ";", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "has", + "t": "source.perl.6 storage.type.variable.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " $.y;", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "method", + "t": "source.perl.6 storage.type.declare.routine.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " distance( Point ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$p", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " ) {", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "sqrt", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "((", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$!x", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " - ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$p", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": ") ** 2 + (", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$!y", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " - ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$p", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".y) ** 2)", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " }", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "method", + "t": "source.perl.6 storage.type.declare.routine.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " distance-", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "to", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "-center {", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "self", + "t": "source.perl.6 variable.language.perl", + "r": { + "dark_plus": "variable.language: #569CD6", + "light_plus": "variable.language: #0000FF", + "dark_vs": "variable.language: #569CD6", + "light_vs": "variable.language: #0000FF", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable.language: #569CD6", + "hc_light": "variable.language: #0F4A85", + "light_modern": "variable.language: #0000FF" + } + }, + { + "c": ".distance: Point.", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "new", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " => 0, y => 0)", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " }", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "my", + "t": "source.perl.6 storage.type.variable.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " = Point.", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "new", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "( ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " => 1.2, y => -3.7 );", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "say", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\"", + "t": "source.perl.6 string.quoted.double.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "Point's location: (", + "t": "source.perl.6 string.quoted.double.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "\"", + "t": "source.perl.6 string.quoted.double.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6 string.quoted.single.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".y, ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ")", + "t": "source.perl.6 string.quoted.single.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ";", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": " OUTPUT: Point's location: (1.2, -3.7)", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": " Changing x and y (note methods \"x\" and \"y\" used as lvalues):", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " = 3;", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".y = 4;", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "say", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\"", + "t": "source.perl.6 string.quoted.double.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "Point's location: (", + "t": "source.perl.6 string.quoted.double.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "\"", + "t": "source.perl.6 string.quoted.double.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6 string.quoted.single.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ", ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".y, ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.begin.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ")", + "t": "source.perl.6 string.quoted.single.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": "'", + "t": "source.perl.6 string.quoted.single.perl punctuation.definition.string.end.perl", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" + } + }, + { + "c": ";", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": " OUTPUT: Point's location: (3, 4)", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "my", + "t": "source.perl.6 storage.type.variable.perl", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6", + "dark_modern": "storage.type: #569CD6", + "hc_light": "storage.type: #0F4A85", + "light_modern": "storage.type: #0000FF" + } + }, + { + "c": " ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$other-point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " = Point.", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "new", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "x", + "t": "source.perl.6 keyword.operator.perl", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " => -5, y => 10);", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".distance(", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "$other-point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": "); ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "=> 10", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "$point", + "t": "source.perl.6 variable.other.identifier.perl.6", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": ".distance-", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "to", + "t": "source.perl.6 support.function.perl", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "-center; ", + "t": "source.perl.6", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.perl.6 comment.line.number-sign.perl punctuation.definition.comment.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + }, + { + "c": "=> 5", + "t": "source.perl.6 comment.line.number-sign.perl", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "dark_modern": "comment: #6A9955", + "hc_light": "comment: #515151", + "light_modern": "comment: #008000" + } + } +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock index 4c166d0a3c8..a7a6fa446ca 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== -jsonc-parser@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== diff --git a/package.json b/package.json index bf70dc18097..8e9dd05a1b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.83.0", - "distro": "46e7bb69af9f06de037c3d7e8f61de4a679f9ef1", + "distro": "c13fa037c20b672a48c9e7990df10998974196f9", "author": { "name": "Microsoft Corporation" }, @@ -94,14 +94,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.61", - "xterm-addon-canvas": "0.5.0-beta.22", - "xterm-addon-image": "0.6.0-beta.14", - "xterm-addon-search": "0.13.0-beta.20", - "xterm-addon-serialize": "0.11.0-beta.20", - "xterm-addon-unicode11": "0.6.0-beta.12", - "xterm-addon-webgl": "0.16.0-beta.30", - "xterm-headless": "5.3.0-beta.61", + "xterm": "5.4.0-beta.19", + "xterm-addon-canvas": "0.6.0-beta.19", + "xterm-addon-image": "0.6.0-beta.21", + "xterm-addon-search": "0.14.0-beta.18", + "xterm-addon-serialize": "0.12.0-beta.18", + "xterm-addon-unicode11": "0.7.0-beta.18", + "xterm-addon-webgl": "0.17.0-beta.18", + "xterm-headless": "5.4.0-beta.19", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -148,7 +148,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "25.8.0", + "electron": "25.8.1", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", @@ -210,7 +210,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.2.7", - "typescript": "^5.3.0-dev.20230905", + "typescript": "^5.3.0-dev.20230911", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/remote/package.json b/remote/package.json index f064462fc27..50c98b47a48 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,14 +26,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.61", - "xterm-addon-canvas": "0.5.0-beta.22", - "xterm-addon-image": "0.6.0-beta.14", - "xterm-addon-search": "0.13.0-beta.20", - "xterm-addon-serialize": "0.11.0-beta.20", - "xterm-addon-unicode11": "0.6.0-beta.12", - "xterm-addon-webgl": "0.16.0-beta.30", - "xterm-headless": "5.3.0-beta.61", + "xterm": "5.4.0-beta.19", + "xterm-addon-canvas": "0.6.0-beta.19", + "xterm-addon-image": "0.6.0-beta.21", + "xterm-addon-search": "0.14.0-beta.18", + "xterm-addon-serialize": "0.12.0-beta.18", + "xterm-addon-unicode11": "0.7.0-beta.18", + "xterm-addon-webgl": "0.17.0-beta.18", + "xterm-headless": "5.4.0-beta.19", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index 6075a563e34..6a4f9fdd64b 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,11 +11,11 @@ "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.0.0", - "xterm": "5.3.0-beta.61", - "xterm-addon-canvas": "0.5.0-beta.22", - "xterm-addon-image": "0.6.0-beta.14", - "xterm-addon-search": "0.13.0-beta.20", - "xterm-addon-unicode11": "0.6.0-beta.12", - "xterm-addon-webgl": "0.16.0-beta.30" + "xterm": "5.4.0-beta.19", + "xterm-addon-canvas": "0.6.0-beta.19", + "xterm-addon-image": "0.6.0-beta.21", + "xterm-addon-search": "0.14.0-beta.18", + "xterm-addon-unicode11": "0.7.0-beta.18", + "xterm-addon-webgl": "0.17.0-beta.18" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index b56fac51a45..f79ce7f8f9c 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -68,32 +68,32 @@ vscode-textmate@9.0.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== -xterm-addon-canvas@0.5.0-beta.22: - version "0.5.0-beta.22" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.22.tgz#513f0c2b7cf96073f47627b27e8965c1b1a22431" - integrity sha512-9F6ZI0DMRgffVfHkLkDwl5n8VscvCaV10tWI3skXOX7Y7Aws6OEeglkOPoU3IllofCU792kHKM4pPoToUxTltg== +xterm-addon-canvas@0.6.0-beta.19: + version "0.6.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.19.tgz#c9e01330a548fbb243d731728e70ae77e6dc8c4f" + integrity sha512-S2tZXqnAqkLA5r40gbOlciR7CLtfztn0lk/ko8Bol08pgSqEzWKF0yadYpsezHRmemvTSmyePCtOTqDrEgFQBA== -xterm-addon-image@0.6.0-beta.14: - version "0.6.0-beta.14" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.14.tgz#75fc3f824123183a4bbb5306e22f8b2c6966b0a6" - integrity sha512-D5Gh5JTKhHaPt1KwQNf6diF37KA4eToJw3XId1wy62tWmSqfq+QflhOGTfd+SnSQYCktU05ETzM+0tncIU62pQ== +xterm-addon-image@0.6.0-beta.21: + version "0.6.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" + integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.13.0-beta.20: - version "0.13.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.20.tgz#8ddd0513e2a70fcefa325722100d2e1bfaf3b9cb" - integrity sha512-wrx6187cJ1UenGL6ZeYv3jFvRPhhENTfbC+Hv1Fnww8LmsKhcj+0+Pm6yInNjX/9hNVsNzdqKyqNeEMoykyoyA== +xterm-addon-search@0.14.0-beta.18: + version "0.14.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.18.tgz#6298b34d9c590f3e3acdd101eb297eaf8cb1f298" + integrity sha512-1CF2bPz9/vQR+q7OFgjvbBRQ0rUSkiKlwZJMnizgbKl6qx0GNg15T52J+l8zLgg8HavlF8aVps1co1A+N5PPZA== -xterm-addon-unicode11@0.6.0-beta.12: - version "0.6.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0-beta.12.tgz#ac6df9d635325dc692e4c602e74a2fc27a09405c" - integrity sha512-9wWWf/5nFafYgq0pn9EgAWnXaXGleVxfjNOqavpLRYFv0nw42QbaYyGvnGcxyYHM5Aqx/8rYE/DDVWZBqQZdYA== +xterm-addon-unicode11@0.7.0-beta.18: + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.18.tgz#8e47b5be3ce5f07a136ca66919f548680da96648" + integrity sha512-5Zy2Kn7kSYQP2ItPV9HNsX2u6/XajB6uyRZ/tx5U79XjZIOMMtPLki56fiGxBOlyhIHFr96bwRlvYKZcEY1ndQ== -xterm-addon-webgl@0.16.0-beta.30: - version "0.16.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.30.tgz#820d5c65f868b14ec4177bfb8a294931a53616bf" - integrity sha512-39qPHPFmNENxcHf8/CzGHS6wzKMMegoRkHB1+scqtBhSxFaD8tX5Ye33HZIEdQ9nXe9xtr4FWVp77T+n9hdrew== +xterm-addon-webgl@0.17.0-beta.18: + version "0.17.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.18.tgz#0ce7b2ed4f1aaefff0eb59dac5c0e2fbba08d9ec" + integrity sha512-F94/+Koo98fAwVr8zFw4vYnmZPKyN6K4ZQTDM2ICozBHtiVWgR2PjhBP8covD6vXwbsnrwq5aipJLbhBMeZ60w== -xterm@5.3.0-beta.61: - version "5.3.0-beta.61" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.61.tgz#a6c27d90a5314da51d80deeb32f3bd77f1e1c8f6" - integrity sha512-rJHpCc48GSpHnu0SSERynQ80D5ikvFVsqhv6JdmeONTrnAFRr134OglJRIpbi2YK8UPbV6F6Dfqm/AQh+9GZzA== +xterm@5.4.0-beta.19: + version "5.4.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.19.tgz#5177e37c8e885aa5cbbb0b1972e9df7634a8aa33" + integrity sha512-eM5UmMf3ml8NIBixEwH5CKj5rgwiZhE51W8xhs7js1GIGVusG/1SL7gS6d/n9UlspnAvQtUOIqzc70x887m6jQ== diff --git a/remote/yarn.lock b/remote/yarn.lock index e2a07ab9843..1771063a82c 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -667,45 +667,45 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-canvas@0.5.0-beta.22: - version "0.5.0-beta.22" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.22.tgz#513f0c2b7cf96073f47627b27e8965c1b1a22431" - integrity sha512-9F6ZI0DMRgffVfHkLkDwl5n8VscvCaV10tWI3skXOX7Y7Aws6OEeglkOPoU3IllofCU792kHKM4pPoToUxTltg== +xterm-addon-canvas@0.6.0-beta.19: + version "0.6.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.19.tgz#c9e01330a548fbb243d731728e70ae77e6dc8c4f" + integrity sha512-S2tZXqnAqkLA5r40gbOlciR7CLtfztn0lk/ko8Bol08pgSqEzWKF0yadYpsezHRmemvTSmyePCtOTqDrEgFQBA== -xterm-addon-image@0.6.0-beta.14: - version "0.6.0-beta.14" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.14.tgz#75fc3f824123183a4bbb5306e22f8b2c6966b0a6" - integrity sha512-D5Gh5JTKhHaPt1KwQNf6diF37KA4eToJw3XId1wy62tWmSqfq+QflhOGTfd+SnSQYCktU05ETzM+0tncIU62pQ== +xterm-addon-image@0.6.0-beta.21: + version "0.6.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" + integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.13.0-beta.20: - version "0.13.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.20.tgz#8ddd0513e2a70fcefa325722100d2e1bfaf3b9cb" - integrity sha512-wrx6187cJ1UenGL6ZeYv3jFvRPhhENTfbC+Hv1Fnww8LmsKhcj+0+Pm6yInNjX/9hNVsNzdqKyqNeEMoykyoyA== +xterm-addon-search@0.14.0-beta.18: + version "0.14.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.18.tgz#6298b34d9c590f3e3acdd101eb297eaf8cb1f298" + integrity sha512-1CF2bPz9/vQR+q7OFgjvbBRQ0rUSkiKlwZJMnizgbKl6qx0GNg15T52J+l8zLgg8HavlF8aVps1co1A+N5PPZA== -xterm-addon-serialize@0.11.0-beta.20: - version "0.11.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.20.tgz#e879b34d214761403f1081833f9221c6903bf0c3" - integrity sha512-OXnC1SATaz7kEFjFWhyv9MJaXi8yHdPjazpGLNi11h33CRTKtCQiqqPBHU87dztnXmpEX6Jw0/jr3zlyXuAmnw== +xterm-addon-serialize@0.12.0-beta.18: + version "0.12.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.18.tgz#0d9369acb49fa01f124dfff064a17f4874211f1a" + integrity sha512-RyV6iU/KRC3QN29i3iaWzm33ACbi0gMQW+LjKSFJN/XrNO1QTqfnh2VZp58G32IFG8l2sg/FUs2gXtEB5IMlhA== -xterm-addon-unicode11@0.6.0-beta.12: - version "0.6.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0-beta.12.tgz#ac6df9d635325dc692e4c602e74a2fc27a09405c" - integrity sha512-9wWWf/5nFafYgq0pn9EgAWnXaXGleVxfjNOqavpLRYFv0nw42QbaYyGvnGcxyYHM5Aqx/8rYE/DDVWZBqQZdYA== +xterm-addon-unicode11@0.7.0-beta.18: + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.18.tgz#8e47b5be3ce5f07a136ca66919f548680da96648" + integrity sha512-5Zy2Kn7kSYQP2ItPV9HNsX2u6/XajB6uyRZ/tx5U79XjZIOMMtPLki56fiGxBOlyhIHFr96bwRlvYKZcEY1ndQ== -xterm-addon-webgl@0.16.0-beta.30: - version "0.16.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.30.tgz#820d5c65f868b14ec4177bfb8a294931a53616bf" - integrity sha512-39qPHPFmNENxcHf8/CzGHS6wzKMMegoRkHB1+scqtBhSxFaD8tX5Ye33HZIEdQ9nXe9xtr4FWVp77T+n9hdrew== +xterm-addon-webgl@0.17.0-beta.18: + version "0.17.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.18.tgz#0ce7b2ed4f1aaefff0eb59dac5c0e2fbba08d9ec" + integrity sha512-F94/+Koo98fAwVr8zFw4vYnmZPKyN6K4ZQTDM2ICozBHtiVWgR2PjhBP8covD6vXwbsnrwq5aipJLbhBMeZ60w== -xterm-headless@5.3.0-beta.61: - version "5.3.0-beta.61" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.61.tgz#28654550cb572709b99ea3eb8672d4568ae141c9" - integrity sha512-yfkbPLUtKjE4K7DsZ204A1BuOKpu6Usqi6rIYWT4XRMi+LjnkTbBjGr2BSjyJ3Gmtm+cSgBD0SvRN+V3xNxbxA== +xterm-headless@5.4.0-beta.19: + version "5.4.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.19.tgz#cdbad09917bdbeeae9197c87663d603fc2794212" + integrity sha512-lLHbZ0DUBoolt4kWCchBAZDlDDdWROwIFxzG8sK389/Z7AlVWsA7kasz2TIPN+l9SC7MrhDdWIFwi1Z0eVODcg== -xterm@5.3.0-beta.61: - version "5.3.0-beta.61" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.61.tgz#a6c27d90a5314da51d80deeb32f3bd77f1e1c8f6" - integrity sha512-rJHpCc48GSpHnu0SSERynQ80D5ikvFVsqhv6JdmeONTrnAFRr134OglJRIpbi2YK8UPbV6F6Dfqm/AQh+9GZzA== +xterm@5.4.0-beta.19: + version "5.4.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.19.tgz#5177e37c8e885aa5cbbb0b1972e9df7634a8aa33" + integrity sha512-eM5UmMf3ml8NIBixEwH5CKj5rgwiZhE51W8xhs7js1GIGVusG/1SL7gS6d/n9UlspnAvQtUOIqzc70x887m6jQ== yallist@^4.0.0: version "4.0.0" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 55d7202968f..fb9498937f6 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -35,7 +35,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% +set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% echo. echo ### API tests (folder) diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index b6f3ec01538..85e4f80dea6 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -44,7 +44,7 @@ echo # Tests in the extension host -API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" +API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then kill_app() { true; } diff --git a/scripts/test-remote-integration.bat b/scripts/test-remote-integration.bat index e0fda5d1c4e..c583f906503 100644 --- a/scripts/test-remote-integration.bat +++ b/scripts/test-remote-integration.bat @@ -55,7 +55,7 @@ echo Storing log files into '%VSCODELOGSDIR%' :: Tests in the extension host -set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% +set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% echo. echo ### API tests (folder) diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index 8163e796645..d3e9f2a5265 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -68,7 +68,7 @@ else kill_app() { killall $INTEGRATION_TEST_APP_NAME || true; } fi -API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --disable-keytar --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" +API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --use-inmemory-secretstorage --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" echo "Storing crash reports into '$VSCODECRASHDIR'." echo "Storing log files into '$VSCODELOGSDIR'." diff --git a/src/typings/thenable.d.ts b/src/typings/thenable.d.ts index f4d8608929d..73373eadbae 100644 --- a/src/typings/thenable.d.ts +++ b/src/typings/thenable.d.ts @@ -9,13 +9,4 @@ * enables reusing existing code without migrating to a specific promise implementation. Still, * we recommend the use of native promises which are available in VS Code. */ -interface Thenable { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; -} +interface Thenable extends PromiseLike { } diff --git a/src/vs/base/browser/iframe.ts b/src/vs/base/browser/iframe.ts index 40ca2f9f507..2a32decc492 100644 --- a/src/vs/base/browser/iframe.ts +++ b/src/vs/base/browser/iframe.ts @@ -123,3 +123,26 @@ export class IframeUtils { }; } } + +/** + * Returns a sha-256 composed of `parentOrigin` and `salt` converted to base 32 + */ +export async function parentOriginHash(parentOrigin: string, salt: string): Promise { + // This same code is also inlined at `src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html` + if (!crypto.subtle) { + throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`); + } + + const strData = JSON.stringify({ parentOrigin, salt }); + const encoder = new TextEncoder(); + const arrData = encoder.encode(strData); + const hash = await crypto.subtle.digest('sha-256', arrData); + return sha256AsBase32(hash); +} + +function sha256AsBase32(bytes: ArrayBuffer): string { + const array = Array.from(new Uint8Array(bytes)); + const hexArray = array.map(b => b.toString(16).padStart(2, '0')).join(''); + // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 + return BigInt(`0x${hexArray}`).toString(32).padStart(52, '0'); +} diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 5ef1672c3f1..0fe7149d05a 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -6,7 +6,7 @@ import * as DomUtils from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, markAsSingleton, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; export namespace EventType { @@ -99,7 +99,7 @@ export class Gesture extends Disposable { return Disposable.None; } if (!Gesture.INSTANCE) { - Gesture.INSTANCE = new Gesture(); + Gesture.INSTANCE = markAsSingleton(new Gesture()); } const remove = Gesture.INSTANCE.targets.push(element); @@ -111,7 +111,7 @@ export class Gesture extends Disposable { return Disposable.None; } if (!Gesture.INSTANCE) { - Gesture.INSTANCE = new Gesture(); + Gesture.INSTANCE = markAsSingleton(new Gesture()); } const remove = Gesture.INSTANCE.ignoreTargets.push(element); diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 9445c64286a..bdc630dbcf7 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -248,7 +248,7 @@ class BranchNode implements ISplitView, IDisposable { readonly element: HTMLElement; readonly children: Node[] = []; - private splitview: SplitView; + private splitview: SplitView; private _size: number; get size(): number { return this._size; } @@ -511,14 +511,27 @@ class BranchNode implements ISplitView, IDisposable { this.onDidChildrenChange(); } - removeChild(index: number, sizing?: Sizing): void { + removeChild(index: number, sizing?: Sizing): Node { index = validateIndex(index, this.children.length); - this.splitview.removeView(index, sizing); + const result = this.splitview.removeView(index, sizing); this.children.splice(index, 1); this.updateBoundarySashes(); this.onDidChildrenChange(); + + return result; + } + + removeAllChildren(): Node[] { + const result = this.splitview.removeAllViews(); + + this.children.splice(0, this.children.length); + + this.updateBoundarySashes(); + this.onDidChildrenChange(); + + return result; } moveChild(from: number, to: number): void { @@ -904,7 +917,10 @@ export interface INodeDescriptor { visible?: boolean; } -function flipNode(node: T, size: number, orthogonalSize: number): T { +function flipNode(node: BranchNode, size: number, orthogonalSize: number): BranchNode; +function flipNode(node: LeafNode, size: number, orthogonalSize: number): LeafNode; +function flipNode(node: Node, size: number, orthogonalSize: number): Node; +function flipNode(node: Node, size: number, orthogonalSize: number): Node { if (node instanceof BranchNode) { const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.splitviewProportionalLayout, size, orthogonalSize, node.edgeSnapping); @@ -925,9 +941,12 @@ function flipNode(node: T, size: number, orthogonalSize: number) result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0, true); } - return result as T; + node.dispose(); + return result; } else { - return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), node.layoutController, orthogonalSize) as T; + const result = new LeafNode(node.view, orthogonal(node.orientation), node.layoutController, orthogonalSize); + node.dispose(); + return result; } } @@ -1162,8 +1181,13 @@ export class GridView implements IDisposable { if (parent instanceof BranchNode) { const node = new LeafNode(view, orthogonal(parent.orientation), this.layoutController, parent.orthogonalSize); - parent.addChild(node, size, index); + try { + parent.addChild(node, size, index); + } catch (err) { + node.dispose(); + throw err; + } } else { const [, grandParent] = tail(pathToParent); const [, parentIndex] = tail(rest); @@ -1175,7 +1199,8 @@ export class GridView implements IDisposable { newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize); } - grandParent.removeChild(parentIndex); + const oldChild = grandParent.removeChild(parentIndex); + oldChild.dispose(); const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize, grandParent.edgeSnapping); grandParent.addChild(newParent, parent.size, parentIndex); @@ -1218,6 +1243,7 @@ export class GridView implements IDisposable { } parent.removeChild(index, sizing); + node.dispose(); if (parent.children.length === 0) { throw new Error('Invalid grid state'); @@ -1237,6 +1263,7 @@ export class GridView implements IDisposable { // we must promote sibling to be the new root parent.removeChild(0); + parent.dispose(); this.root = sibling; this.boundarySashes = this.boundarySashes; this.trySet2x2(); @@ -1246,19 +1273,20 @@ export class GridView implements IDisposable { const [, grandParent] = tail(pathToParent); const [, parentIndex] = tail(rest); - const sibling = parent.children[0]; const isSiblingVisible = parent.isChildVisible(0); - parent.removeChild(0); + const sibling = parent.removeChild(0); const sizes = grandParent.children.map((_, i) => grandParent.getChildSize(i)); grandParent.removeChild(parentIndex, sizing); + parent.dispose(); if (sibling instanceof BranchNode) { sizes.splice(parentIndex, 1, ...sibling.children.map(c => c.size)); - for (let i = 0; i < sibling.children.length; i++) { - const child = sibling.children[i]; - grandParent.addChild(child, child.size, parentIndex + i); + const siblingChildren = sibling.removeAllChildren(); + + for (let i = 0; i < siblingChildren.length; i++) { + grandParent.addChild(siblingChildren[i], siblingChildren[i].size, parentIndex + i); } } else { const newSibling = new LeafNode(sibling.view, orthogonal(sibling.orientation), this.layoutController, sibling.size); @@ -1266,6 +1294,8 @@ export class GridView implements IDisposable { grandParent.addChild(newSibling, sizing, parentIndex); } + sibling.dispose(); + for (let i = 0; i < sizes.length; i++) { grandParent.resizeChild(i, sizes[i]); } @@ -1654,9 +1684,6 @@ export class GridView implements IDisposable { dispose(): void { this.onDidSashResetRelay.dispose(); this.root.dispose(); - - if (this.element && this.element.parentElement) { - this.element.parentElement.removeChild(this.element); - } + this.element.parentElement?.removeChild(this.element); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 28f18d42537..05b3e5a45f7 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -119,7 +119,7 @@ export interface IView { /** * A descriptor for a {@link SplitView} instance. */ -export interface ISplitViewDescriptor { +export interface ISplitViewDescriptor = IView> { /** * The layout size of the {@link SplitView}. @@ -150,11 +150,11 @@ export interface ISplitViewDescriptor { * * @defaultValue `true` */ - readonly view: IView; + readonly view: TView; }[]; } -export interface ISplitViewOptions { +export interface ISplitViewOptions = IView> { /** * Which axis the views align on. @@ -184,7 +184,7 @@ export interface ISplitViewOptions { * An initial description of this {@link SplitView} instance, allowing * to initialze all views within the ctor. */ - readonly descriptor?: ISplitViewDescriptor; + readonly descriptor?: ISplitViewDescriptor; /** * The scrollbar visibility setting for whenever the views within @@ -207,7 +207,7 @@ interface ISashEvent { type ViewItemSize = number | { cachedVisibleSize: number }; -abstract class ViewItem { +abstract class ViewItem> { private _size: number; set size(size: number) { @@ -259,7 +259,7 @@ abstract class ViewItem { constructor( protected container: HTMLElement, - readonly view: IView, + readonly view: TView, size: ViewItemSize, private disposable: IDisposable ) { @@ -285,7 +285,7 @@ abstract class ViewItem { } } -class VerticalViewItem extends ViewItem { +class VerticalViewItem> extends ViewItem { layoutContainer(offset: number): void { this.container.style.top = `${offset}px`; @@ -293,7 +293,7 @@ class VerticalViewItem extends ViewItem { } } -class HorizontalViewItem extends ViewItem { +class HorizontalViewItem> extends ViewItem { layoutContainer(offset: number): void { this.container.style.left = `${offset}px`; @@ -413,7 +413,7 @@ export namespace Sizing { * - View swap/move support * - Alt key modifier behavior, macOS style */ -export class SplitView extends Disposable { +export class SplitView = IView> extends Disposable { /** * This {@link SplitView}'s orientation. @@ -433,7 +433,7 @@ export class SplitView extends Disposable { private layoutContext: TLayoutContext | undefined; private contentSize = 0; private proportions: (number | undefined)[] | undefined = undefined; - private viewItems: ViewItem[] = []; + private viewItems: ViewItem[] = []; sashItems: ISashItem[] = []; // used in tests private sashDragState: ISashDragState | undefined; private state: State = State.Idle; @@ -549,7 +549,7 @@ export class SplitView extends Disposable { /** * Create a new {@link SplitView} instance. */ - constructor(container: HTMLElement, options: ISplitViewOptions = {}) { + constructor(container: HTMLElement, options: ISplitViewOptions = {}) { super(); this.orientation = options.orientation ?? Orientation.VERTICAL; @@ -636,7 +636,7 @@ export class SplitView extends Disposable { * @param index The index to insert the view on. * @param skipLayout Whether layout should be skipped. */ - addView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { + addView(view: TView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { this.doAddView(view, size, index, skipLayout); } @@ -646,7 +646,7 @@ export class SplitView extends Disposable { * @param index The index where the {@link IView view} is located. * @param sizing Whether to distribute other {@link IView view}'s sizes. */ - removeView(index: number, sizing?: Sizing): IView { + removeView(index: number, sizing?: Sizing): TView { if (this.state !== State.Idle) { throw new Error('Cant modify splitview'); } @@ -695,6 +695,31 @@ export class SplitView extends Disposable { return result; } + removeAllViews(): TView[] { + if (this.state !== State.Idle) { + throw new Error('Cant modify splitview'); + } + + this.state = State.Busy; + + const viewItems = this.viewItems.splice(0, this.viewItems.length); + + for (const viewItem of viewItems) { + viewItem.dispose(); + } + + const sashItems = this.sashItems.splice(0, this.sashItems.length); + + for (const sashItem of sashItems) { + sashItem.disposable.dispose(); + } + + this.relayout(); + this.state = State.Idle; + + return viewItems.map(i => i.view); + } + /** * Move a {@link IView view} to a different index. * @@ -951,7 +976,7 @@ export class SplitView extends Disposable { } } - private onViewChange(item: ViewItem, size: number | undefined): void { + private onViewChange(item: ViewItem, size: number | undefined): void { const index = this.viewItems.indexOf(item); if (index < 0 || index >= this.viewItems.length) { @@ -1024,7 +1049,7 @@ export class SplitView extends Disposable { * Distribute the entire {@link SplitView} size among all {@link IView views}. */ distributeViewSizes(): void { - const flexibleViewItems: ViewItem[] = []; + const flexibleViewItems: ViewItem[] = []; let flexibleSize = 0; for (const item of this.viewItems) { @@ -1058,7 +1083,7 @@ export class SplitView extends Disposable { return this.viewItems[index].size; } - private doAddView(view: IView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { + private doAddView(view: TView, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void { if (this.state !== State.Idle) { throw new Error('Cant modify splitview'); } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 1780b30b4ff..b42e5cdb612 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -265,7 +265,7 @@ export function toAction(props: { id: string; label: string; enabled?: boolean; class: undefined, enabled: props.enabled ?? true, checked: props.checked ?? false, - run: async () => props.run(), + run: async (...args: unknown[]) => props.run(), tooltip: props.label }; } diff --git a/src/vs/base/common/arraysFind.ts b/src/vs/base/common/arraysFind.ts index 8ef2d2ad936..7b24f5ade42 100644 --- a/src/vs/base/common/arraysFind.ts +++ b/src/vs/base/common/arraysFind.ts @@ -5,7 +5,7 @@ import { Comparator } from './arrays'; -export function findLast(array: readonly T[], predicate: (item: T) => boolean): T | undefined { +export function findLast(array: readonly T[], predicate: (item: T) => boolean, fromIdx?: number): T | undefined { const idx = findLastIdx(array, predicate); if (idx === -1) { return undefined; @@ -13,8 +13,8 @@ export function findLast(array: readonly T[], predicate: (item: T) => boolean return array[idx]; } -export function findLastIdx(array: readonly T[], predicate: (item: T) => boolean): number { - for (let i = array.length - 1; i >= 0; i--) { +export function findLastIdx(array: readonly T[], predicate: (item: T) => boolean, fromIndex = array.length - 1): number { + for (let i = fromIndex; i >= 0; i--) { const element = array[i]; if (predicate(element)) { diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index dad5135fd56..8ce413f6e70 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -182,7 +182,7 @@ export class Throttler implements IDisposable { queue(promiseFactory: ITask>): Promise { if (this.isDisposed) { - throw new Error('Throttler is disposed'); + return Promise.reject(new Error('Throttler is disposed')); } if (this.activePromise) { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 2e5f7fc27a1..b8e1da4b777 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -394,7 +394,7 @@ export namespace Event { * this.onInstallExtension = Event.buffer(service.onInstallExtension, true); * ``` */ - export function buffer(event: Event, flushAfterTimeout = false, _buffer: T[] = []): Event { + export function buffer(event: Event, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event { let buffer: T[] | null = _buffer.slice(); let listener: IDisposable | null = event(e => { @@ -405,6 +405,10 @@ export namespace Event { } }); + if (disposable) { + disposable.add(listener); + } + const flush = () => { buffer?.forEach(e => emitter.fire(e)); buffer = null; @@ -414,6 +418,9 @@ export namespace Event { onWillAddFirstListener() { if (!listener) { listener = event(e => emitter.fire(e)); + if (disposable) { + disposable.add(listener); + } } }, @@ -435,6 +442,10 @@ export namespace Event { } }); + if (disposable) { + disposable.add(emitter); + } + return emitter.event; } /** diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 3d5dd09598d..0afa0bbd79b 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { compareBy, numberComparator } from 'vs/base/common/arrays'; +import { SetMap, groupBy } from 'vs/base/common/collections'; import { once } from 'vs/base/common/functional'; import { Iterable } from 'vs/base/common/iterator'; @@ -41,6 +43,157 @@ export interface IDisposableTracker { markAsSingleton(disposable: IDisposable): void; } +export interface DisposableInfo { + value: IDisposable; + source: string | null; + parent: IDisposable | null; + isSingleton: boolean; + idx: number; +} + +export class DisposableTracker implements IDisposableTracker { + private static idx = 0; + + private readonly livingDisposables = new Map(); + + private getDisposableData(d: IDisposable): DisposableInfo { + let val = this.livingDisposables.get(d); + if (!val) { + val = { parent: null, source: null, isSingleton: false, value: d, idx: DisposableTracker.idx++ }; + this.livingDisposables.set(d, val); + } + return val; + } + + trackDisposable(d: IDisposable): void { + const data = this.getDisposableData(d); + if (!data.source) { + data.source = + new Error().stack!; + } + } + + setParent(child: IDisposable, parent: IDisposable | null): void { + const data = this.getDisposableData(child); + data.parent = parent; + } + + markAsDisposed(x: IDisposable): void { + this.livingDisposables.delete(x); + } + + markAsSingleton(disposable: IDisposable): void { + this.getDisposableData(disposable).isSingleton = true; + } + + private getRootParent(data: DisposableInfo, cache: Map): DisposableInfo { + const cacheValue = cache.get(data); + if (cacheValue) { + return cacheValue; + } + + const result = data.parent ? this.getRootParent(this.getDisposableData(data.parent), cache) : data; + cache.set(data, result); + return result; + } + + getTrackedDisposables(): IDisposable[] { + const rootParentCache = new Map(); + + const leaking = [...this.livingDisposables.entries()] + .filter(([, v]) => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton) + .map(([k]) => k) + .flat(); + + return leaking; + } + + computeLeakingDisposables(maxReported = 10, preComputedLeaks?: DisposableInfo[]): { leaks: DisposableInfo[]; details: string } | undefined { + let uncoveredLeakingObjs: DisposableInfo[] | undefined; + if (preComputedLeaks) { + uncoveredLeakingObjs = preComputedLeaks; + } else { + const rootParentCache = new Map(); + + const leakingObjects = [...this.livingDisposables.values()] + .filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton); + + if (leakingObjects.length === 0) { + return; + } + const leakingObjsSet = new Set(leakingObjects.map(o => o.value)); + + // Remove all objects that are a child of other leaking objects. Assumes there are no cycles. + uncoveredLeakingObjs = leakingObjects.filter(l => { + return !(l.parent && leakingObjsSet.has(l.parent)); + }); + + if (uncoveredLeakingObjs.length === 0) { + throw new Error('There are cyclic diposable chains!'); + } + } + + if (!uncoveredLeakingObjs) { + return undefined; + } + + function getStackTracePath(leaking: DisposableInfo): string[] { + function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) { + while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) { + array.shift(); + } + } + + const lines = leaking.source!.split('\n').map(p => p.trim().replace('at ', '')).filter(l => l !== ''); + removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]); + return lines.reverse(); + } + + const stackTraceStarts = new SetMap(); + for (const leaking of uncoveredLeakingObjs) { + const stackTracePath = getStackTracePath(leaking); + for (let i = 0; i <= stackTracePath.length; i++) { + stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking); + } + } + + // Put earlier leaks first + uncoveredLeakingObjs.sort(compareBy(l => l.idx, numberComparator)); + + let message = ''; + + let i = 0; + for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) { + i++; + const stackTracePath = getStackTracePath(leaking); + const stackTraceFormattedLines = []; + + for (let i = 0; i < stackTracePath.length; i++) { + let line = stackTracePath[i]; + const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n')); + line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`; + + const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n')); + const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v); + delete continuations[stackTracePath[i]]; + for (const [cont, set] of Object.entries(continuations)) { + stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); + } + + stackTraceFormattedLines.unshift(line); + } + + message += `\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`; + } + + if (uncoveredLeakingObjs.length > maxReported) { + message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`; + } + + return { leaks: uncoveredLeakingObjs, details: message }; + } +} + export function setDisposableTracker(tracker: IDisposableTracker | null): void { disposableTracker = tracker; } diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 31e345a0bf2..966b07b191c 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -58,6 +58,7 @@ export interface IObservable { * (see {@link ConvenientObservable.map}). */ map(fn: (value: T, reader: IReader) => TNew): IObservable; + map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; /** * A human-readable name for debugging purposes. @@ -165,9 +166,15 @@ export abstract class ConvenientObservable implements IObservable(fn: (value: T, reader: IReader) => TNew): IObservable { + public map(fn: (value: T, reader: IReader) => TNew): IObservable; + public map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; + public map(fnOrOwner: object | ((value: T, reader: IReader) => TNew), fnOrUndefined?: (value: T, reader: IReader) => TNew): IObservable { + const owner = fnOrUndefined === undefined ? undefined : fnOrOwner as object; + const fn = fnOrUndefined === undefined ? fnOrOwner as (value: T, reader: IReader) => TNew : fnOrUndefined; + return _derived( { + owner, debugName: () => { const name = getFunctionName(fn); if (name !== undefined) { @@ -180,7 +187,10 @@ export abstract class ConvenientObservable implements IObservable fn(this.read(reader), reader), diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 0f1abf6043e..53c2a3a7eee 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -26,7 +26,7 @@ export function derived(computeFnOrOwner: ((reader: IReader) => T) | object, export function derivedOpts( options: { owner?: object; - debugName?: string | (() => string); + debugName?: string | (() => string | undefined); equalityComparer?: EqualityComparer; }, computeFn: (reader: IReader) => T diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts index 1d9b7938169..07599e730bc 100644 --- a/src/vs/base/common/paging.ts +++ b/src/vs/base/common/paging.ts @@ -118,7 +118,7 @@ export class PagedModel implements IPagedModel { }); } - cancellationToken.onCancellationRequested(() => { + const listener = cancellationToken.onCancellationRequested(() => { if (!page.cts) { return; } @@ -132,7 +132,8 @@ export class PagedModel implements IPagedModel { page.promiseIndexes.add(index); - return page.promise.then(() => page.elements[indexInPage]); + return page.promise.then(() => page.elements[indexInPage]) + .finally(() => listener.dispose()); } } diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 767d0a5aec6..cc4c65a625f 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -107,12 +107,12 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok let last = createCancelablePromise(() => Promise.resolve()); let extractedEntriesCount = 0; - token.onCancellationRequested(() => { + const listener = token.onCancellationRequested(() => { last.cancel(); zipfile.close(); }); - return new Promise((c, e) => { + return new Promise((c, e) => { const throttler = new Sequencer(); const readNextEntry = (token: CancellationToken) => { @@ -158,7 +158,7 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null, e)); }); - }); + }).finally(() => listener.dispose()); } function openZip(zipFile: string, lazy: boolean = false): Promise { diff --git a/src/vs/base/parts/ipc/common/ipc.mp.ts b/src/vs/base/parts/ipc/common/ipc.mp.ts index 052f58252b7..14b7e210e8e 100644 --- a/src/vs/base/parts/ipc/common/ipc.mp.ts +++ b/src/vs/base/parts/ipc/common/ipc.mp.ts @@ -74,5 +74,7 @@ export class Client extends IPCClient implements IDisposable { override dispose(): void { this.protocol.disconnect(); + + super.dispose(); } } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 747e8513b1f..db61aed4ff1 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -427,7 +427,6 @@ export class ChannelServer implements IChannelServer { this.sendResponse({ id, data, type: ResponseType.PromiseSuccess }); - this.activeRequests.delete(request.id); }, err => { if (err instanceof Error) { this.sendResponse({ @@ -440,7 +439,8 @@ export class ChannelServer implements IChannelServer{ id, data: err, type: ResponseType.PromiseErrorObj }); } - + }).finally(() => { + disposable.dispose(); this.activeRequests.delete(request.id); }); @@ -639,7 +639,10 @@ export class ChannelClient implements IChannelClient, IDisposable { this.activeRequests.add(disposable); }); - return result.finally(() => { this.activeRequests.delete(disposable); }); + return result.finally(() => { + disposable.dispose(); + this.activeRequests.delete(disposable); + }); } private requestEvent(channelName: string, name: string, arg?: any): Event { @@ -795,6 +798,8 @@ export class IPCServer implements IChannelServer, I private readonly _onDidRemoveConnection = new Emitter>(); readonly onDidRemoveConnection: Event> = this._onDidRemoveConnection.event; + private disposables = new DisposableStore(); + get connections(): Connection[] { const result: Connection[] = []; this._connections.forEach(ctx => result.push(ctx)); @@ -802,10 +807,10 @@ export class IPCServer implements IChannelServer, I } constructor(onDidClientConnect: Event) { - onDidClientConnect(({ protocol, onDidClientDisconnect }) => { + this.disposables.add(onDidClientConnect(({ protocol, onDidClientDisconnect }) => { const onFirstMessage = Event.once(protocol.onMessage); - onFirstMessage(msg => { + this.disposables.add(onFirstMessage(msg => { const reader = new BufferReader(msg); const ctx = deserialize(reader) as TContext; @@ -818,14 +823,14 @@ export class IPCServer implements IChannelServer, I this._connections.add(connection); this._onDidAddConnection.fire(connection); - onDidClientDisconnect(() => { + this.disposables.add(onDidClientDisconnect(() => { channelServer.dispose(); channelClient.dispose(); this._connections.delete(connection); this._onDidRemoveConnection.fire(connection); - }); - }); - }); + })); + })); + })); } /** @@ -879,7 +884,7 @@ export class IPCServer implements IChannelServer, I private getMulticastEvent(channelName: string, clientFilter: (client: Client) => boolean, eventName: string, arg: any): Event { const that = this; - let disposables = new DisposableStore(); + let disposables: DisposableStore | undefined; // Create an emitter which hooks up to all clients // as soon as first listener is added. It also @@ -922,7 +927,8 @@ export class IPCServer implements IChannelServer, I disposables.add(eventMultiplexer); }, onDidRemoveLastListener: () => { - disposables.dispose(); + disposables?.dispose(); + disposables = undefined; } }); @@ -932,14 +938,21 @@ export class IPCServer implements IChannelServer, I registerChannel(channelName: string, channel: IServerChannel): void { this.channels.set(channelName, channel); - this._connections.forEach(connection => { + for (const connection of this._connections) { connection.channelServer.registerChannel(channelName, channel); - }); + } } dispose(): void { - this.channels.clear(); + this.disposables.dispose(); + + for (const connection of this._connections) { + connection.channelClient.dispose(); + connection.channelServer.dispose(); + } + this._connections.clear(); + this.channels.clear(); this._onDidAddConnection.dispose(); this._onDidRemoveConnection.dispose(); } @@ -1074,7 +1087,7 @@ export namespace ProxyChannel { export interface ICreateServiceChannelOptions extends IProxyOptions { } - export function fromService(service: unknown, options?: ICreateServiceChannelOptions): IServerChannel { + export function fromService(service: unknown, disposables: DisposableStore, options?: ICreateServiceChannelOptions): IServerChannel { const handler = service as { [key: string]: unknown }; const disableMarshalling = options && options.disableMarshalling; @@ -1083,7 +1096,7 @@ export namespace ProxyChannel { const mapEventNameToEvent = new Map>(); for (const key in handler) { if (propertyIsEvent(key)) { - mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); + mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true, undefined, disposables)); } } diff --git a/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts b/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts index fe8167fa9bd..00813be686f 100644 --- a/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts +++ b/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('IPC, MessagePorts', () => { @@ -55,4 +56,6 @@ suite('IPC, MessagePorts', () => { client1.dispose(); client2.dispose(); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/base/parts/ipc/test/common/ipc.test.ts b/src/vs/base/parts/ipc/test/common/ipc.test.ts index eaead87178e..35184c08217 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -9,9 +9,11 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { BufferReader, BufferWriter, ClientConnectionEvent, deserialize, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel, serialize } from 'vs/base/parts/ipc/common/ipc'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class QueueProtocol implements IMessagePassingProtocol { @@ -111,6 +113,8 @@ interface ITestService { class TestService implements ITestService { + private disposables = new DisposableStore(); + private readonly _onPong = new Emitter(); readonly onPong = this._onPong.event; @@ -131,7 +135,7 @@ class TestService implements ITestService { return Promise.reject(canceled()); } - return new Promise((_, e) => cancellationToken.onCancellationRequested(() => e(canceled()))); + return new Promise((_, e) => this.disposables.add(cancellationToken.onCancellationRequested(() => e(canceled())))); } buffersLength(buffers: VSBuffer[]): Promise { @@ -149,6 +153,10 @@ class TestService implements ITestService { context(context?: unknown): Promise { return Promise.resolve(context); } + + dispose() { + this.disposables.dispose(); + } } class TestChannel implements IServerChannel { @@ -213,6 +221,8 @@ class TestChannelClient implements ITestService { suite('Base IPC', function () { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('createProtocolPair', async function () { const [clientProtocol, serverProtocol] = createProtocolPair(); @@ -236,21 +246,16 @@ suite('Base IPC', function () { let ipcService: ITestService; setup(function () { - service = new TestService(); - const testServer = new TestIPCServer(); + service = store.add(new TestService()); + const testServer = store.add(new TestIPCServer()); server = testServer; server.registerChannel(TestChannelId, new TestChannel(service)); - client = testServer.createConnection('client1'); + client = store.add(testServer.createConnection('client1')); ipcService = new TestChannelClient(client.getChannel(TestChannelId)); }); - teardown(function () { - client.dispose(); - server.dispose(); - }); - test('call success', async function () { const r = await ipcService.marco(); return assert.strictEqual(r, 'polo'); @@ -301,7 +306,7 @@ suite('Base IPC', function () { test('listen to events', async function () { const messages: string[] = []; - ipcService.onPong(msg => messages.push(msg)); + store.add(ipcService.onPong(msg => messages.push(msg))); await timeout(0); assert.deepStrictEqual(messages, []); @@ -343,20 +348,21 @@ suite('Base IPC', function () { let service: TestService; let ipcService: ITestService; + const disposables = new DisposableStore(); + setup(function () { - service = new TestService(); - const testServer = new TestIPCServer(); + service = store.add(new TestService()); + const testServer = disposables.add(new TestIPCServer()); server = testServer; - server.registerChannel(TestChannelId, ProxyChannel.fromService(service)); + server.registerChannel(TestChannelId, ProxyChannel.fromService(service, disposables)); - client = testServer.createConnection('client1'); + client = disposables.add(testServer.createConnection('client1')); ipcService = ProxyChannel.toService(client.getChannel(TestChannelId)); }); teardown(function () { - client.dispose(); - server.dispose(); + disposables.clear(); }); test('call success', async function () { @@ -376,7 +382,7 @@ suite('Base IPC', function () { test('listen to events', async function () { const messages: string[] = []; - ipcService.onPong(msg => messages.push(msg)); + disposables.add(ipcService.onPong(msg => messages.push(msg))); await timeout(0); assert.deepStrictEqual(messages, []); @@ -409,20 +415,21 @@ suite('Base IPC', function () { let service: TestService; let ipcService: ITestService; + const disposables = new DisposableStore(); + setup(function () { - service = new TestService(); - const testServer = new TestIPCServer(); + service = store.add(new TestService()); + const testServer = disposables.add(new TestIPCServer()); server = testServer; - server.registerChannel(TestChannelId, ProxyChannel.fromService(service)); + server.registerChannel(TestChannelId, ProxyChannel.fromService(service, disposables)); - client = testServer.createConnection('client1'); + client = disposables.add(testServer.createConnection('client1')); ipcService = ProxyChannel.toService(client.getChannel(TestChannelId), { context: 'Super Context' }); }); teardown(function () { - client.dispose(); - server.dispose(); + disposables.clear(); }); test('call extra context', async function () { @@ -433,20 +440,20 @@ suite('Base IPC', function () { suite('one to many', function () { test('all clients get pinged', async function () { - const service = new TestService(); + const service = store.add(new TestService()); const channel = new TestChannel(service); - const server = new TestIPCServer(); + const server = store.add(new TestIPCServer()); server.registerChannel('channel', channel); let client1GotPinged = false; - const client1 = server.createConnection('client1'); + const client1 = store.add(server.createConnection('client1')); const ipcService1 = new TestChannelClient(client1.getChannel('channel')); - ipcService1.onPong(() => client1GotPinged = true); + store.add(ipcService1.onPong(() => client1GotPinged = true)); let client2GotPinged = false; - const client2 = server.createConnection('client2'); + const client2 = store.add(server.createConnection('client2')); const ipcService2 = new TestChannelClient(client2.getChannel('channel')); - ipcService2.onPong(() => client2GotPinged = true); + store.add(ipcService2.onPong(() => client2GotPinged = true)); await timeout(1); service.ping('hello'); @@ -454,24 +461,20 @@ suite('Base IPC', function () { await timeout(1); assert(client1GotPinged, 'client 1 got pinged'); assert(client2GotPinged, 'client 2 got pinged'); - - client1.dispose(); - client2.dispose(); - server.dispose(); }); test('server gets pings from all clients (broadcast channel)', async function () { - const server = new TestIPCServer(); + const server = store.add(new TestIPCServer()); const client1 = server.createConnection('client1'); - const clientService1 = new TestService(); + const clientService1 = store.add(new TestService()); const clientChannel1 = new TestChannel(clientService1); client1.registerChannel('channel', clientChannel1); const pings: string[] = []; const channel = server.getChannel('channel', () => true); const service = new TestChannelClient(channel); - service.onPong(msg => pings.push(msg)); + store.add(service.onPong(msg => pings.push(msg))); await timeout(1); clientService1.ping('hello 1'); @@ -480,7 +483,7 @@ suite('Base IPC', function () { assert.deepStrictEqual(pings, ['hello 1']); const client2 = server.createConnection('client2'); - const clientService2 = new TestService(); + const clientService2 = store.add(new TestService()); const clientChannel2 = new TestChannel(clientService2); client2.registerChannel('channel', clientChannel2); @@ -503,7 +506,6 @@ suite('Base IPC', function () { assert.deepStrictEqual(pings, ['hello 1', 'hello 2', 'hello again 2']); client2.dispose(); - server.dispose(); }); }); }); diff --git a/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts b/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts index 06f5451ce77..44559562cea 100644 --- a/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts +++ b/src/vs/base/parts/ipc/test/electron-sandbox/ipc.mp.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('IPC, MessagePorts', () => { @@ -27,4 +28,6 @@ suite('IPC, MessagePorts', () => { client1.dispose(); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 0351a7b42b3..eb9607e6c3e 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -392,6 +392,10 @@ export class Storage extends Disposable implements IStorage { } private async doFlush(delay?: number): Promise { + if (this.options.hint === StorageHint.STORAGE_IN_MEMORY) { + return this.flushPending(); // return early if in-memory + } + return this.flushDelayer.trigger(() => this.flushPending(), delay); } diff --git a/src/vs/base/test/browser/actionbar.test.ts b/src/vs/base/test/browser/actionbar.test.ts index 5c593ce1b69..fc119f36de9 100644 --- a/src/vs/base/test/browser/actionbar.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -6,16 +6,19 @@ import * as assert from 'assert'; import { ActionBar, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, Separator } from 'vs/base/common/actions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Actionbar', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('prepareActions()', function () { const a1 = new Separator(); const a2 = new Separator(); - const a3 = new Action('a3'); + const a3 = store.add(new Action('a3')); const a4 = new Separator(); const a5 = new Separator(); - const a6 = new Action('a6'); + const a6 = store.add(new Action('a6')); const a7 = new Separator(); const actions = prepareActions([a1, a2, a3, a4, a5, a6, a7]); @@ -27,10 +30,10 @@ suite('Actionbar', () => { test('hasAction()', function () { const container = document.createElement('div'); - const actionbar = new ActionBar(container); + const actionbar = store.add(new ActionBar(container)); - const a1 = new Action('a1'); - const a2 = new Action('a2'); + const a1 = store.add(new Action('a1')); + const a2 = store.add(new Action('a2')); actionbar.push(a1); assert.strictEqual(actionbar.hasAction(a1), true); diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index 8dcbb829d7b..b4e5e4d6c16 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -8,6 +8,8 @@ import { createSerializedGrid, Direction, getRelativeLocation, Grid, GridNode, G import { Event } from 'vs/base/common/event'; import { deepClone } from 'vs/base/common/objects'; import { nodesToArrays, TestView } from './util'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { DisposableStore } from 'vs/base/common/lifecycle'; // Simple example: // @@ -30,6 +32,8 @@ import { nodesToArrays, TestView } from './util'; // +-3 suite('Grid', function () { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); let container: HTMLElement; setup(function () { @@ -72,8 +76,8 @@ suite('Grid', function () { }); test('empty', () => { - const view1 = new TestView(100, Number.MAX_VALUE, 100, Number.MAX_VALUE); - const gridview = new Grid(view1); + const view1 = store.add(new TestView(100, Number.MAX_VALUE, 100, Number.MAX_VALUE)); + const gridview = store.add(new Grid(view1)); container.appendChild(gridview.element); gridview.layout(800, 600); @@ -81,59 +85,59 @@ suite('Grid', function () { }); test('two views vertically', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); assert.deepStrictEqual(view1.size, [800, 400]); assert.deepStrictEqual(view2.size, [800, 200]); }); test('two views horizontally', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 300, view1, Direction.Right); assert.deepStrictEqual(view1.size, [500, 600]); assert.deepStrictEqual(view2.size, [300, 600]); }); test('simple layout', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); assert.deepStrictEqual(view1.size, [800, 400]); assert.deepStrictEqual(view2.size, [800, 200]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); assert.deepStrictEqual(view1.size, [600, 400]); assert.deepStrictEqual(view2.size, [800, 200]); assert.deepStrictEqual(view3.size, [200, 400]); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); assert.deepStrictEqual(view1.size, [600, 400]); assert.deepStrictEqual(view2.size, [600, 200]); assert.deepStrictEqual(view3.size, [200, 400]); assert.deepStrictEqual(view4.size, [200, 200]); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); assert.deepStrictEqual(view1.size, [600, 300]); assert.deepStrictEqual(view2.size, [600, 200]); @@ -143,32 +147,32 @@ suite('Grid', function () { }); test('another simple layout with automatic size distribution', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Left); assert.deepStrictEqual(view1.size, [400, 600]); assert.deepStrictEqual(view2.size, [400, 600]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view1, Direction.Right); assert.deepStrictEqual(view1.size, [266, 600]); assert.deepStrictEqual(view2.size, [266, 600]); assert.deepStrictEqual(view3.size, [268, 600]); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Distribute, view2, Direction.Down); assert.deepStrictEqual(view1.size, [266, 600]); assert.deepStrictEqual(view2.size, [266, 300]); assert.deepStrictEqual(view3.size, [268, 600]); assert.deepStrictEqual(view4.size, [266, 300]); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, Sizing.Distribute, view3, Direction.Up); assert.deepStrictEqual(view1.size, [266, 600]); assert.deepStrictEqual(view2.size, [266, 300]); @@ -176,7 +180,7 @@ suite('Grid', function () { assert.deepStrictEqual(view4.size, [266, 300]); assert.deepStrictEqual(view5.size, [268, 300]); - const view6 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view6 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view6, Sizing.Distribute, view3, Direction.Down); assert.deepStrictEqual(view1.size, [266, 600]); assert.deepStrictEqual(view2.size, [266, 300]); @@ -187,32 +191,32 @@ suite('Grid', function () { }); test('another simple layout with split size distribution', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Left); assert.deepStrictEqual(view1.size, [400, 600]); assert.deepStrictEqual(view2.size, [400, 600]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Split, view1, Direction.Right); assert.deepStrictEqual(view1.size, [200, 600]); assert.deepStrictEqual(view2.size, [400, 600]); assert.deepStrictEqual(view3.size, [200, 600]); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Split, view2, Direction.Down); assert.deepStrictEqual(view1.size, [200, 600]); assert.deepStrictEqual(view2.size, [400, 300]); assert.deepStrictEqual(view3.size, [200, 600]); assert.deepStrictEqual(view4.size, [400, 300]); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, Sizing.Split, view3, Direction.Up); assert.deepStrictEqual(view1.size, [200, 600]); assert.deepStrictEqual(view2.size, [400, 300]); @@ -220,7 +224,7 @@ suite('Grid', function () { assert.deepStrictEqual(view4.size, [400, 300]); assert.deepStrictEqual(view5.size, [200, 300]); - const view6 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view6 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view6, Sizing.Split, view3, Direction.Down); assert.deepStrictEqual(view1.size, [200, 600]); assert.deepStrictEqual(view2.size, [400, 300]); @@ -231,32 +235,32 @@ suite('Grid', function () { }); test('3/2 layout with split', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); assert.deepStrictEqual(view1.size, [800, 600]); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Down); assert.deepStrictEqual(view1.size, [800, 300]); assert.deepStrictEqual(view2.size, [800, 300]); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Split, view2, Direction.Right); assert.deepStrictEqual(view1.size, [800, 300]); assert.deepStrictEqual(view2.size, [400, 300]); assert.deepStrictEqual(view3.size, [400, 300]); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Split, view1, Direction.Right); assert.deepStrictEqual(view1.size, [400, 300]); assert.deepStrictEqual(view2.size, [400, 300]); assert.deepStrictEqual(view3.size, [400, 300]); assert.deepStrictEqual(view4.size, [400, 300]); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, Sizing.Split, view1, Direction.Right); assert.deepStrictEqual(view1.size, [200, 300]); assert.deepStrictEqual(view2.size, [400, 300]); @@ -266,19 +270,19 @@ suite('Grid', function () { }); test('sizing should be correct after branch demotion #50564', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Right); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Split, view2, Direction.Down); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Split, view2, Direction.Right); assert.deepStrictEqual(view1.size, [400, 600]); assert.deepStrictEqual(view2.size, [200, 300]); @@ -292,19 +296,19 @@ suite('Grid', function () { }); test('sizing should be correct after branch demotion #50675', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Down); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Distribute, view3, Direction.Right); assert.deepStrictEqual(view1.size, [800, 200]); assert.deepStrictEqual(view2.size, [800, 200]); @@ -318,8 +322,8 @@ suite('Grid', function () { }); test('getNeighborViews should work on single view layout', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); @@ -336,16 +340,16 @@ suite('Grid', function () { }); test('getNeighborViews should work on simple layout', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Down); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up), []); @@ -380,22 +384,22 @@ suite('Grid', function () { }); test('getNeighborViews should work on a complex layout', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Down); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Distribute, view2, Direction.Right); - const view5 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, Sizing.Distribute, view4, Direction.Down); assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Up), []); @@ -421,19 +425,19 @@ suite('Grid', function () { }); test('getNeighborViews should work on another simple layout', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Right); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Distribute, view2, Direction.Right); assert.deepStrictEqual(grid.getNeighborViews(view4, Direction.Up), []); @@ -443,19 +447,19 @@ suite('Grid', function () { }); test('getNeighborViews should only return immediate neighbors', function () { - const view1 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new Grid(view1); + const view1 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new Grid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Distribute, view1, Direction.Right); - const view3 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Distribute, view2, Direction.Down); - const view4 = new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestView(50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Distribute, view2, Direction.Right); assert.deepStrictEqual(grid.getNeighborViews(view1, Direction.Right), [view2, view3]); @@ -483,8 +487,10 @@ class TestViewDeserializer implements IViewDeserializer { private views = new Map(); + constructor(private readonly store: Pick) { } + fromJSON(json: any): TestSerializableView { - const view = new TestSerializableView(json.name, 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view = this.store.add(new TestSerializableView(json.name, 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); this.views.set(json.name, view); return view; } @@ -508,6 +514,7 @@ function nodesToNames(node: GridNode): any { suite('SerializableGrid', function () { + const store = ensureNoDisposablesAreLeakedInTestSuite(); let container: HTMLElement; setup(function () { @@ -518,8 +525,8 @@ suite('SerializableGrid', function () { }); test('serialize empty', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); @@ -545,21 +552,21 @@ suite('SerializableGrid', function () { }); test('serialize simple layout', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); - const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); assert.deepStrictEqual(grid.serialize(), { @@ -599,45 +606,45 @@ suite('SerializableGrid', function () { }); test('deserialize empty', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); grid2.layout(800, 600); assert.deepStrictEqual(nodesToNames(grid2.getViews()), ['view1']); }); test('deserialize simple layout', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); - const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); @@ -657,29 +664,29 @@ suite('SerializableGrid', function () { }); test('deserialize simple layout with scaling', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); - const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); @@ -696,25 +703,25 @@ suite('SerializableGrid', function () { }); test('deserialize 4 view layout (ben issue #2)', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Down); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Split, view2, Direction.Down); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, Sizing.Split, view3, Direction.Right); const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); @@ -730,20 +737,20 @@ suite('SerializableGrid', function () { }); test('deserialize 2 view layout (ben issue #3)', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Right); const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); @@ -755,16 +762,16 @@ suite('SerializableGrid', function () { }); test('deserialize simple view layout #50609', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, Sizing.Split, view1, Direction.Right); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, Sizing.Split, view2, Direction.Down); grid.removeView(view1, Sizing.Split); @@ -772,8 +779,8 @@ suite('SerializableGrid', function () { const json = grid.serialize(); grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view2Copy = deserializer.getView('view2'); const view3Copy = deserializer.getView('view3'); @@ -835,7 +842,7 @@ suite('SerializableGrid', function () { } }; - const grid = SerializableGrid.deserialize(serializedGrid, deserializer); + const grid = store.add(SerializableGrid.deserialize(serializedGrid, deserializer)); assert.strictEqual(views.length, 3); // should not throw @@ -867,21 +874,21 @@ suite('SerializableGrid', function () { }); test('serialize should store visibility and previous size', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); - const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); assert.deepStrictEqual(view1.size, [600, 300]); @@ -954,8 +961,8 @@ suite('SerializableGrid', function () { grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); @@ -994,21 +1001,21 @@ suite('SerializableGrid', function () { }); test('serialize should store visibility and previous size even for first leaf', function () { - const view1 = new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); - const grid = new SerializableGrid(view1); + const view1 = store.add(new TestSerializableView('view1', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); + const grid = store.add(new SerializableGrid(view1)); container.appendChild(grid.element); grid.layout(800, 600); - const view2 = new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view2 = store.add(new TestSerializableView('view2', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view2, 200, view1, Direction.Up); - const view3 = new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view3 = store.add(new TestSerializableView('view3', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view3, 200, view1, Direction.Right); - const view4 = new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view4 = store.add(new TestSerializableView('view4', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view4, 200, view2, Direction.Left); - const view5 = new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE); + const view5 = store.add(new TestSerializableView('view5', 50, Number.MAX_VALUE, 50, Number.MAX_VALUE)); grid.addView(view5, 100, view1, Direction.Down); assert.deepStrictEqual(view1.size, [600, 300]); @@ -1063,8 +1070,8 @@ suite('SerializableGrid', function () { grid.dispose(); - const deserializer = new TestViewDeserializer(); - const grid2 = SerializableGrid.deserialize(json, deserializer); + const deserializer = new TestViewDeserializer(store); + const grid2 = store.add(SerializableGrid.deserialize(json, deserializer)); const view1Copy = deserializer.getView('view1'); const view2Copy = deserializer.getView('view2'); diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index fbe6fd47b58..e9be1b00e16 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -7,36 +7,41 @@ import * as assert from 'assert'; import { $ } from 'vs/base/browser/dom'; import { GridView, IView, Orientation, Sizing } from 'vs/base/browser/ui/grid/gridview'; import { nodesToArrays, TestView } from './util'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Gridview', function () { - let gridview: GridView; - setup(function () { - gridview = new GridView(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + function createGridView(): GridView { + const gridview = store.add(new GridView()); const container = $('.container'); container.style.position = 'absolute'; container.style.width = `${200}px`; container.style.height = `${200}px`; container.appendChild(gridview.element); - }); + + return gridview; + } test('empty gridview is empty', function () { + const gridview = createGridView(); assert.deepStrictEqual(nodesToArrays(gridview.getView()), []); - gridview.dispose(); }); test('gridview addView', function () { + const gridview = createGridView(); - const view = new TestView(20, 20, 20, 20); + const view = store.add(new TestView(20, 20, 20, 20)); assert.throws(() => gridview.addView(view, 200, []), 'empty location'); assert.throws(() => gridview.addView(view, 200, [1]), 'index overflow'); assert.throws(() => gridview.addView(view, 200, [0, 0]), 'hierarchy overflow'); const views = [ - new TestView(20, 20, 20, 20), - new TestView(20, 20, 20, 20), - new TestView(20, 20, 20, 20) + store.add(new TestView(20, 20, 20, 20)), + store.add(new TestView(20, 20, 20, 20)), + store.add(new TestView(20, 20, 20, 20)) ]; gridview.addView(views[0], 200, [0]); @@ -44,17 +49,16 @@ suite('Gridview', function () { gridview.addView(views[2], 200, [2]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), views); - - gridview.dispose(); }); test('gridview addView nested', function () { + const gridview = createGridView(); const views = [ - new TestView(20, 20, 20, 20), + store.add(new TestView(20, 20, 20, 20)), [ - new TestView(20, 20, 20, 20), - new TestView(20, 20, 20, 20) + store.add(new TestView(20, 20, 20, 20)), + store.add(new TestView(20, 20, 20, 20)) ] ]; @@ -63,63 +67,61 @@ suite('Gridview', function () { gridview.addView((views[1] as TestView[])[1] as IView, 200, [1, 1]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), views); - - gridview.dispose(); }); test('gridview addView deep nested', function () { + const gridview = createGridView(); - const view1 = new TestView(20, 20, 20, 20); + const view1 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view1 as IView, 200, [0]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1]); - const view2 = new TestView(20, 20, 20, 20); + const view2 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view2 as IView, 200, [1]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, view2]); - const view3 = new TestView(20, 20, 20, 20); + const view3 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view3 as IView, 200, [1, 0]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]); - const view4 = new TestView(20, 20, 20, 20); + const view4 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view4 as IView, 200, [1, 0, 0]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]); - const view5 = new TestView(20, 20, 20, 20); + const view5 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view5 as IView, 200, [1, 0]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]); - const view6 = new TestView(20, 20, 20, 20); + const view6 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view6 as IView, 200, [2]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]); - const view7 = new TestView(20, 20, 20, 20); + const view7 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view7 as IView, 200, [1, 1]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]); - const view8 = new TestView(20, 20, 20, 20); + const view8 = store.add(new TestView(20, 20, 20, 20)); gridview.addView(view8 as IView, 200, [1, 1, 0]); assert.deepStrictEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]); - - gridview.dispose(); }); test('simple layout', function () { + const gridview = createGridView(); gridview.layout(800, 600); - const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view1 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view1, 200, [0]); assert.deepStrictEqual(view1.size, [800, 600]); assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); - const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view2 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view2, 200, [0]); assert.deepStrictEqual(view1.size, [800, 400]); assert.deepStrictEqual(gridview.getViewSize([1]), { width: 800, height: 400 }); assert.deepStrictEqual(view2.size, [800, 200]); assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 200 }); - const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view3 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view3, 200, [1, 1]); assert.deepStrictEqual(view1.size, [600, 400]); assert.deepStrictEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); @@ -128,7 +130,7 @@ suite('Gridview', function () { assert.deepStrictEqual(view3.size, [200, 400]); assert.deepStrictEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 }); - const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view4 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view4, 200, [0, 0]); assert.deepStrictEqual(view1.size, [600, 400]); assert.deepStrictEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 }); @@ -139,7 +141,7 @@ suite('Gridview', function () { assert.deepStrictEqual(view4.size, [200, 200]); assert.deepStrictEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 }); - const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view5 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view5, 100, [1, 0, 1]); assert.deepStrictEqual(view1.size, [600, 300]); assert.deepStrictEqual(gridview.getViewSize([1, 0, 0]), { width: 600, height: 300 }); @@ -154,32 +156,33 @@ suite('Gridview', function () { }); test('simple layout with automatic size distribution', function () { + const gridview = createGridView(); gridview.layout(800, 600); - const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view1 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view1, Sizing.Distribute, [0]); assert.deepStrictEqual(view1.size, [800, 600]); assert.deepStrictEqual(gridview.getViewSize([0]), { width: 800, height: 600 }); - const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view2 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view2, Sizing.Distribute, [0]); assert.deepStrictEqual(view1.size, [800, 300]); assert.deepStrictEqual(view2.size, [800, 300]); - const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view3 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view3, Sizing.Distribute, [1, 1]); assert.deepStrictEqual(view1.size, [400, 300]); assert.deepStrictEqual(view2.size, [800, 300]); assert.deepStrictEqual(view3.size, [400, 300]); - const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view4 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view4, Sizing.Distribute, [0, 0]); assert.deepStrictEqual(view1.size, [400, 300]); assert.deepStrictEqual(view2.size, [400, 300]); assert.deepStrictEqual(view3.size, [400, 300]); assert.deepStrictEqual(view4.size, [400, 300]); - const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view5 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view5, Sizing.Distribute, [1, 0, 1]); assert.deepStrictEqual(view1.size, [400, 150]); assert.deepStrictEqual(view2.size, [400, 300]); @@ -189,14 +192,15 @@ suite('Gridview', function () { }); test('addviews before layout call 1', function () { + const gridview = createGridView(); - const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view1 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view1, 200, [0]); - const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view2 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view2, 200, [0]); - const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view3 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view3, 200, [1, 1]); gridview.layout(800, 600); @@ -207,13 +211,14 @@ suite('Gridview', function () { }); test('addviews before layout call 2', function () { - const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const gridview = createGridView(); + const view1 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view1, 200, [0]); - const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view2 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view2, 200, [0]); - const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view3 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view3, 200, [0, 0]); gridview.layout(800, 600); @@ -224,10 +229,11 @@ suite('Gridview', function () { }); test('flipping orientation should preserve absolute offsets', function () { - const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const gridview = createGridView(); + const view1 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view1, 200, [0]); - const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + const view2 = store.add(new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY)); gridview.addView(view2, 200, [1]); gridview.layout(800, 600, 100, 200); diff --git a/src/vs/base/test/browser/ui/list/listView.test.ts b/src/vs/base/test/browser/ui/list/listView.test.ts index da3ee3e7913..7dcab8e7d25 100644 --- a/src/vs/base/test/browser/ui/list/listView.test.ts +++ b/src/vs/base/test/browser/ui/list/listView.test.ts @@ -7,8 +7,11 @@ import * as assert from 'assert'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ListView } from 'vs/base/browser/ui/list/listView'; import { range } from 'vs/base/common/arrays'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ListView', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('all rows get disposed', function () { const element = document.createElement('div'); element.style.height = '200px'; diff --git a/src/vs/base/test/browser/ui/list/listWidget.test.ts b/src/vs/base/test/browser/ui/list/listWidget.test.ts index 995445f3500..28fd9524341 100644 --- a/src/vs/base/test/browser/ui/list/listWidget.test.ts +++ b/src/vs/base/test/browser/ui/list/listWidget.test.ts @@ -11,7 +11,7 @@ import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ListWidget', function () { - const ds = ensureNoDisposablesAreLeakedInTestSuite(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); test('Page up and down', async function () { const element = document.createElement('div'); @@ -32,8 +32,7 @@ suite('ListWidget', function () { disposeTemplate() { templatesCount--; } }; - const listWidget = new List('test', element, delegate, [renderer]); - ds.add(listWidget); + const listWidget = store.add(new List('test', element, delegate, [renderer])); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); @@ -55,8 +54,6 @@ suite('ListWidget', function () { listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page down to previous page'); - - listWidget.dispose(); }); test('Page up and down with item taller than viewport #149502', async function () { @@ -78,8 +75,7 @@ suite('ListWidget', function () { disposeTemplate() { templatesCount--; } }; - const listWidget = new List('test', element, delegate, [renderer]); - ds.add(listWidget); + const listWidget = store.add(new List('test', element, delegate, [renderer])); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); @@ -96,7 +92,5 @@ suite('ListWidget', function () { listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page up to next page'); - - listWidget.dispose(); }); }); diff --git a/src/vs/base/test/browser/ui/list/rangeMap.test.ts b/src/vs/base/test/browser/ui/list/rangeMap.test.ts index 518b250a943..0171250324b 100644 --- a/src/vs/base/test/browser/ui/list/rangeMap.test.ts +++ b/src/vs/base/test/browser/ui/list/rangeMap.test.ts @@ -6,13 +6,11 @@ import * as assert from 'assert'; import { consolidate, groupIntersect, RangeMap } from 'vs/base/browser/ui/list/rangeMap'; import { Range } from 'vs/base/common/range'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('RangeMap', () => { - let rangeMap: RangeMap; - setup(() => { - rangeMap = new RangeMap(); - }); + ensureNoDisposablesAreLeakedInTestSuite(); test('intersection', () => { assert.deepStrictEqual(Range.intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 }); @@ -141,6 +139,7 @@ suite('RangeMap', () => { }); test('empty', () => { + const rangeMap = new RangeMap(); assert.strictEqual(rangeMap.size, 0); assert.strictEqual(rangeMap.count, 0); }); @@ -152,30 +151,35 @@ suite('RangeMap', () => { const ten = { size: 10 }; test('length & count', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one]); assert.strictEqual(rangeMap.size, 1); assert.strictEqual(rangeMap.count, 1); }); test('length & count #2', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one]); assert.strictEqual(rangeMap.size, 5); assert.strictEqual(rangeMap.count, 5); }); test('length & count #3', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [five]); assert.strictEqual(rangeMap.size, 5); assert.strictEqual(rangeMap.count, 1); }); test('length & count #4', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [five, five, five, five, five]); assert.strictEqual(rangeMap.size, 25); assert.strictEqual(rangeMap.count, 5); }); test('insert', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [five, five, five, five, five]); assert.strictEqual(rangeMap.size, 25); assert.strictEqual(rangeMap.count, 5); @@ -194,6 +198,7 @@ suite('RangeMap', () => { }); test('delete', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [five, five, five, five, five, five, five, five, five, five, five, five, five, five, five, @@ -219,6 +224,7 @@ suite('RangeMap', () => { }); test('insert & delete', () => { + const rangeMap = new RangeMap(); assert.strictEqual(rangeMap.size, 0); assert.strictEqual(rangeMap.count, 0); @@ -232,6 +238,7 @@ suite('RangeMap', () => { }); test('insert & delete #2', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); rangeMap.splice(2, 6); @@ -240,6 +247,7 @@ suite('RangeMap', () => { }); test('insert & delete #3', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one, two, two, two, two, two, @@ -249,7 +257,8 @@ suite('RangeMap', () => { assert.strictEqual(rangeMap.size, 24); }); - test('insert & delete #3', () => { + test('insert & delete #4', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one, two, two, two, two, two, @@ -265,6 +274,7 @@ suite('RangeMap', () => { suite('indexAt, positionAt', () => { test('empty', () => { + const rangeMap = new RangeMap(); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(10), 0); assert.strictEqual(rangeMap.indexAt(-1), -1); @@ -274,6 +284,7 @@ suite('RangeMap', () => { }); test('simple', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one]); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(1), 1); @@ -282,6 +293,7 @@ suite('RangeMap', () => { }); test('simple #2', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [ten]); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(5), 0); @@ -292,6 +304,7 @@ suite('RangeMap', () => { }); test('insert', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(1), 1); @@ -312,6 +325,7 @@ suite('RangeMap', () => { }); test('delete', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]); rangeMap.splice(2, 6); @@ -327,6 +341,7 @@ suite('RangeMap', () => { }); test('delete #2', () => { + const rangeMap = new RangeMap(); rangeMap.splice(0, 0, [ten, ten, ten, ten, ten, ten, ten, ten, ten, ten]); rangeMap.splice(2, 6); @@ -345,13 +360,9 @@ suite('RangeMap', () => { }); suite('RangeMap with top padding', () => { - let rangeMap: RangeMap; - - setup(() => { - rangeMap = new RangeMap(10); - }); test('empty', () => { + const rangeMap = new RangeMap(10); assert.strictEqual(rangeMap.size, 10); assert.strictEqual(rangeMap.count, 0); }); @@ -361,30 +372,35 @@ suite('RangeMap with top padding', () => { const ten = { size: 10 }; test('length & count', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [one]); assert.strictEqual(rangeMap.size, 11); assert.strictEqual(rangeMap.count, 1); }); test('length & count #2', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [one, one, one, one, one]); assert.strictEqual(rangeMap.size, 15); assert.strictEqual(rangeMap.count, 5); }); test('length & count #3', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [five]); assert.strictEqual(rangeMap.size, 15); assert.strictEqual(rangeMap.count, 1); }); test('length & count #4', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [five, five, five, five, five]); assert.strictEqual(rangeMap.size, 35); assert.strictEqual(rangeMap.count, 5); }); test('insert', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [five, five, five, five, five]); assert.strictEqual(rangeMap.size, 35); assert.strictEqual(rangeMap.count, 5); @@ -404,6 +420,7 @@ suite('RangeMap with top padding', () => { suite('indexAt, positionAt', () => { test('empty', () => { + const rangeMap = new RangeMap(10); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(10), 0); assert.strictEqual(rangeMap.indexAt(-1), -1); @@ -413,6 +430,7 @@ suite('RangeMap with top padding', () => { }); test('simple', () => { + const rangeMap = new RangeMap(10); rangeMap.splice(0, 0, [one]); assert.strictEqual(rangeMap.indexAt(0), 0); assert.strictEqual(rangeMap.indexAt(1), 0); diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 0f23533882c..48ee43fe1b3 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import { IView, LayoutPriority, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { Emitter } from 'vs/base/common/event'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class TestView implements IView { @@ -66,6 +67,9 @@ function getSashes(splitview: SplitView): Sash[] { } suite('Splitview', () => { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + let container: HTMLElement; setup(() => { @@ -76,16 +80,15 @@ suite('Splitview', () => { }); test('empty splitview has empty DOM', () => { - const splitview = new SplitView(container); + store.add(new SplitView(container)); assert.strictEqual(container.firstElementChild!.firstElementChild!.childElementCount, 0, 'split view should be empty'); - splitview.dispose(); }); test('has views and sashes as children', () => { - const view1 = new TestView(20, 20); - const view2 = new TestView(20, 20); - const view3 = new TestView(20, 20); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, 20)); + const view2 = store.add(new TestView(20, 20)); + const view3 = store.add(new TestView(20, 20)); + const splitview = store.add(new SplitView(container)); splitview.addView(view1, 20); splitview.addView(view2, 20); @@ -120,37 +123,26 @@ suite('Splitview', () => { sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash'); assert.strictEqual(sashQuery.length, 0, 'split view should have no sashes'); - - splitview.dispose(); - view1.dispose(); - view2.dispose(); - view3.dispose(); }); test('calls view methods on addView and removeView', () => { - const view = new TestView(20, 20); - const splitview = new SplitView(container); + const view = store.add(new TestView(20, 20)); + const splitview = store.add(new SplitView(container)); let didLayout = false; - const layoutDisposable = view.onDidLayout(() => didLayout = true); - - const renderDisposable = view.onDidGetElement(() => undefined); + store.add(view.onDidLayout(() => didLayout = true)); + store.add(view.onDidGetElement(() => undefined)); splitview.addView(view, 20); assert.strictEqual(view.size, 20, 'view has right size'); assert(didLayout, 'layout is called'); assert(didLayout, 'render is called'); - - splitview.dispose(); - layoutDisposable.dispose(); - renderDisposable.dispose(); - view.dispose(); }); test('stretches view to viewport', () => { - const view = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view, 20); @@ -170,16 +162,13 @@ suite('Splitview', () => { splitview.layout(200); assert.strictEqual(view.size, 200, 'view is stretched'); - - splitview.dispose(); - view.dispose(); }); test('can resize views', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, 20); @@ -207,18 +196,13 @@ suite('Splitview', () => { assert.strictEqual(view1.size, 70, 'view1 stays the same'); assert.strictEqual(view2.size, 90, 'view2 is collapsed'); assert.strictEqual(view3.size, 40, 'view3 is stretched'); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('reacts to view changes', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, 20); @@ -253,18 +237,13 @@ suite('Splitview', () => { assert.strictEqual(view1.size, 20, 'view1 is collapsed'); assert.strictEqual(view2.size, 80, 'view2 is collapsed'); assert.strictEqual(view3.size, 100, 'view3 is stretched'); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('sashes are properly enabled/disabled', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -307,27 +286,22 @@ suite('Splitview', () => { splitview.resizeView(0, 40); assert.strictEqual(sashes[0].state, SashState.Enabled, 'first sash is enabled'); assert.strictEqual(sashes[1].state, SashState.Enabled, 'second sash is enabled'); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('issue #35497', () => { - const view1 = new TestView(160, Number.POSITIVE_INFINITY); - const view2 = new TestView(66, 66); + const view1 = store.add(new TestView(160, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(66, 66)); - const splitview = new SplitView(container); + const splitview = store.add(new SplitView(container)); splitview.layout(986); splitview.addView(view1, 142, 0); assert.strictEqual(view1.size, 986, 'first view is stretched'); - view2.onDidGetElement(() => { + store.add(view2.onDidGetElement(() => { assert.throws(() => splitview.resizeView(1, 922)); assert.throws(() => splitview.resizeView(1, 922)); - }); + })); splitview.addView(view2, 66, 0); assert.strictEqual(view2.size, 66, 'second view is fixed'); @@ -337,17 +311,13 @@ suite('Splitview', () => { assert.strictEqual(viewContainers.length, 2, 'there are two view containers'); assert.strictEqual((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px'); assert.strictEqual((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px'); - - splitview.dispose(); - view2.dispose(); - view1.dispose(); }); test('automatic size distribution', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -361,18 +331,13 @@ suite('Splitview', () => { splitview.removeView(1, Sizing.Distribute); assert.deepStrictEqual([view1.size, view3.size], [100, 100]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('add views before layout', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.addView(view1, 100); splitview.addView(view2, 75); @@ -380,18 +345,13 @@ suite('Splitview', () => { splitview.layout(200); assert.deepStrictEqual([view1.size, view2.size, view3.size], [67, 67, 66]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('split sizing', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -402,18 +362,13 @@ suite('Splitview', () => { splitview.addView(view3, Sizing.Split(1)); assert.deepStrictEqual([view1.size, view2.size, view3.size], [100, 50, 50]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('split sizing 2', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -424,17 +379,12 @@ suite('Splitview', () => { splitview.addView(view3, Sizing.Split(0)); assert.deepStrictEqual([view1.size, view2.size, view3.size], [50, 100, 50]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('proportional layout', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container)); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -443,16 +393,12 @@ suite('Splitview', () => { splitview.layout(100); assert.deepStrictEqual([view1.size, view2.size], [50, 50]); - - splitview.dispose(); - view2.dispose(); - view1.dispose(); }); test('disable proportional layout', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container, { proportionalLayout: false }); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container, { proportionalLayout: false })); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -461,17 +407,13 @@ suite('Splitview', () => { splitview.layout(100); assert.deepStrictEqual([view1.size, view2.size], [80, 20]); - - splitview.dispose(); - view2.dispose(); - view1.dispose(); }); test('high layout priority', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.High); - const view3 = new TestView(20, Number.POSITIVE_INFINITY); - const splitview = new SplitView(container, { proportionalLayout: false }); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.High)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const splitview = store.add(new SplitView(container, { proportionalLayout: false })); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -490,18 +432,13 @@ suite('Splitview', () => { splitview.layout(200); assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 160, 20]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('low layout priority', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low); - const splitview = new SplitView(container, { proportionalLayout: false }); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low)); + const splitview = store.add(new SplitView(container, { proportionalLayout: false })); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -520,18 +457,13 @@ suite('Splitview', () => { splitview.layout(200); assert.deepStrictEqual([view1.size, view2.size, view3.size], [20, 160, 20]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); test('context propagates to views', () => { - const view1 = new TestView(20, Number.POSITIVE_INFINITY); - const view2 = new TestView(20, Number.POSITIVE_INFINITY); - const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low); - const splitview = new SplitView(container, { proportionalLayout: false }); + const view1 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view2 = store.add(new TestView(20, Number.POSITIVE_INFINITY)); + const view3 = store.add(new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low)); + const splitview = store.add(new SplitView(container, { proportionalLayout: false })); splitview.layout(200); splitview.addView(view1, Sizing.Distribute); @@ -540,10 +472,5 @@ suite('Splitview', () => { splitview.layout(200, 100); assert.deepStrictEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); - - splitview.dispose(); - view3.dispose(); - view2.dispose(); - view1.dispose(); }); }); diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index d10d76b0b85..9a552a91b83 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -9,6 +9,7 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; import { Iterable } from 'vs/base/common/iterator'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface Element { id: string; @@ -86,6 +87,8 @@ class Model { suite('AsyncDataTree', function () { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('Collapse state should be preserved across refresh calls', async () => { const container = document.createElement('div'); @@ -96,7 +99,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() })); tree.layout(200); assert.strictEqual(container.querySelectorAll('.monaco-list-row').length, 0); @@ -144,7 +147,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); @@ -204,7 +207,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); @@ -234,7 +237,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); @@ -276,9 +279,9 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { collapseByDefault: el => el.id !== 'a' - }); + })); tree.layout(200); await tree.setInput(model.root); @@ -308,7 +311,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); tree.layout(200); const pSetInput = tree.setInput(model.root); @@ -351,7 +354,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); tree.layout(200); const pSetInput = tree.setInput(model.root); @@ -380,7 +383,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); @@ -418,7 +421,7 @@ suite('AsyncDataTree', function () { }] }); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); @@ -461,7 +464,7 @@ suite('AsyncDataTree', function () { }); const a = model.get('a'); - const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + const tree = store.add(new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() })); tree.layout(200); await tree.setInput(model.root); diff --git a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts index beee230cbad..79de7324a24 100644 --- a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -9,6 +9,7 @@ import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeModelSetChildrenOptions } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { Iterable } from 'vs/base/common/iterator'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface IResolvedCompressedTreeElement extends ICompressedTreeElement { readonly element: T; @@ -32,6 +33,8 @@ function resolve(treeElement: ICompressedTreeElement): IResolvedCompressed suite('CompressedObjectTree', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + suite('compress & decompress', function () { test('small', function () { diff --git a/src/vs/base/test/browser/ui/tree/dataTree.test.ts b/src/vs/base/test/browser/ui/tree/dataTree.test.ts index ab450c682e7..fb821d3b662 100644 --- a/src/vs/base/test/browser/ui/tree/dataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/dataTree.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface E { value: number; @@ -30,6 +31,10 @@ suite('DataTree', function () { children: [] }; + teardown(() => tree.dispose()); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { const container = document.createElement('div'); container.style.width = '200px'; @@ -67,10 +72,6 @@ suite('DataTree', function () { tree.layout(200); }); - teardown(() => { - tree.dispose(); - }); - test('view state is lost implicitly', () => { tree.setInput(root); diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index 9ae0e08b0f1..008da2a3ea0 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { IIndexTreeModelSpliceOptions, IIndexTreeNode, IList, IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ITreeElement, ITreeFilter, ITreeNode, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function toList(arr: T[]): IList { return { @@ -39,6 +40,8 @@ function withSmartSplice(fn: (options: IIndexTreeModelSpliceOptions suite('IndexTreeModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('ctor', () => { const list: ITreeNode[] = []; const model = new IndexTreeModel('test', toList(list), -1); diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts index f758b089e46..a3e39846a5b 100644 --- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -8,12 +8,21 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { CompressibleObjectTree, ICompressibleTreeRenderer, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ObjectTree', function () { + suite('TreeNavigator', function () { let tree: ObjectTree; let filter = (_: number) => true; + teardown(() => { + tree.dispose(); + filter = (_: number) => true; + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => { const container = document.createElement('div'); container.style.width = '200px'; @@ -39,11 +48,6 @@ suite('ObjectTree', function () { tree.layout(200); }); - teardown(() => { - tree.dispose(); - filter = (_: number) => true; - }); - test('should be able to navigate', () => { tree.setChildren(null, [ { diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 2b9917e4ef9..4c895d94130 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -8,6 +8,7 @@ import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; import { ITreeFilter, ITreeNode, ObjectTreeElementCollapseState, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function toList(arr: T[]): IList { return { @@ -25,6 +26,8 @@ function toArray(list: ITreeNode[]): T[] { suite('ObjectTreeModel', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + test('ctor', () => { const list: ITreeNode[] = []; const model = new ObjectTreeModel('test', toList(list)); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 467708aa2c2..4cb9eceb360 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -11,14 +11,17 @@ import { isCancellationError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Async', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + suite('cancelablePromise', function () { test('set token, don\'t wait for inner promise', function () { let canceled = 0; const promise = async.createCancelablePromise(token => { - token.onCancellationRequested(_ => { canceled += 1; }); + store.add(token.onCancellationRequested(_ => { canceled += 1; })); return new Promise(resolve => { /*never*/ }); }); const result = promise.then(_ => assert.ok(false), err => { @@ -33,7 +36,7 @@ suite('Async', () => { test('cancel despite inner promise being resolved', function () { let canceled = 0; const promise = async.createCancelablePromise(token => { - token.onCancellationRequested(_ => { canceled += 1; }); + store.add(token.onCancellationRequested(_ => { canceled += 1; })); return Promise.resolve(1234); }); const result = promise.then(_ => assert.ok(false), err => { @@ -51,7 +54,7 @@ suite('Async', () => { const cancellablePromise = async.createCancelablePromise(token => { order.push('in callback'); - token.onCancellationRequested(_ => order.push('cancelled')); + store.add(token.onCancellationRequested(_ => order.push('cancelled'))); return Promise.resolve(1234); }); @@ -73,7 +76,7 @@ suite('Async', () => { const cancellablePromise = async.createCancelablePromise(token => { order.push('in callback'); - token.onCancellationRequested(_ => order.push('cancelled')); + store.add(token.onCancellationRequested(_ => order.push('cancelled'))); return new Promise(c => setTimeout(c.bind(1234), 0)); }); @@ -185,9 +188,14 @@ suite('Async', () => { const promises: Promise[] = []; throttler.dispose(); - assert.throws(() => promises.push(throttler.queue(factory))); - assert.strictEqual(factoryCalls, 0); - await Promise.all(promises); + promises.push(throttler.queue(factory)); + + try { + await Promise.all(promises); + assert.fail('should fail'); + } catch (err) { + assert.strictEqual(factoryCalls, 0); + } }); }); @@ -787,52 +795,64 @@ suite('Async', () => { test('cancel executing', async function () { const sequentializer = new async.TaskSequentializer(); + const ctsTimeout = store.add(new CancellationTokenSource()); let pendingCancelled = false; - sequentializer.run(1, async.timeout(1), () => pendingCancelled = true); + const timeout = async.timeout(1, ctsTimeout.token); + sequentializer.run(1, timeout, () => pendingCancelled = true); sequentializer.cancelRunning(); assert.ok(pendingCancelled); + ctsTimeout.cancel(); }); }); test('raceCancellation', async () => { - const cts = new CancellationTokenSource(); + const cts = store.add(new CancellationTokenSource()); + const ctsTimeout = store.add(new CancellationTokenSource()); let triggered = false; - const p = async.raceCancellation(async.timeout(100).then(() => triggered = true), cts.token); + const timeout = async.timeout(100, ctsTimeout.token); + const p = async.raceCancellation(timeout.then(() => triggered = true), cts.token); cts.cancel(); await p; assert.ok(!triggered); + ctsTimeout.cancel(); }); test('raceTimeout', async () => { - const cts = new CancellationTokenSource(); + const cts = store.add(new CancellationTokenSource()); // timeout wins let timedout = false; let triggered = false; - const p1 = async.raceTimeout(async.timeout(100).then(() => triggered = true), 1, () => timedout = true); + const ctsTimeout1 = store.add(new CancellationTokenSource()); + const timeout1 = async.timeout(100, ctsTimeout1.token); + const p1 = async.raceTimeout(timeout1.then(() => triggered = true), 1, () => timedout = true); cts.cancel(); await p1; assert.ok(!triggered); assert.strictEqual(timedout, true); + ctsTimeout1.cancel(); // promise wins timedout = false; - const p2 = async.raceTimeout(async.timeout(1).then(() => triggered = true), 100, () => timedout = true); + const ctsTimeout2 = store.add(new CancellationTokenSource()); + const timeout2 = async.timeout(1, ctsTimeout2.token); + const p2 = async.raceTimeout(timeout2.then(() => triggered = true), 100, () => timedout = true); cts.cancel(); await p2; assert.ok(triggered); assert.strictEqual(timedout, false); + ctsTimeout2.cancel(); }); test('SequencerByKey', async () => { @@ -1111,11 +1131,11 @@ suite('Async', () => { } }; - const worker = new async.ThrottledWorker({ + const worker = store.add(new async.ThrottledWorker({ maxWorkChunkSize: 5, maxBufferedWork: undefined, throttleDelay: 1 - }, handler); + }, handler)); // Work less than chunk size @@ -1223,11 +1243,11 @@ suite('Async', () => { const handled: number[] = []; const handler = (units: readonly number[]) => handled.push(...units); - const worker = new async.ThrottledWorker({ + const worker = store.add(new async.ThrottledWorker({ maxWorkChunkSize: 5, maxBufferedWork: 5, throttleDelay: 1 - }, handler); + }, handler)); let worked = worker.work([1, 2, 3]); assert.strictEqual(worked, true); @@ -1249,11 +1269,11 @@ suite('Async', () => { const handled: number[] = []; const handler = (units: readonly number[]) => handled.push(...units); - const worker = new async.ThrottledWorker({ + const worker = store.add(new async.ThrottledWorker({ maxWorkChunkSize: 5, maxBufferedWork: 5, throttleDelay: 1 - }, handler); + }, handler)); let worked = worker.work([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); assert.strictEqual(worked, false); @@ -1268,11 +1288,11 @@ suite('Async', () => { const handled: number[] = []; const handler = (units: readonly number[]) => handled.push(...units); - const worker = new async.ThrottledWorker({ + const worker = store.add(new async.ThrottledWorker({ maxWorkChunkSize: 5, maxBufferedWork: undefined, throttleDelay: 1 - }, handler); + }, handler)); worker.dispose(); const worked = worker.work([1, 2, 3]); diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/common/buffer.test.ts index 5a37943b658..6c869a16b3f 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/common/buffer.test.ts @@ -7,9 +7,12 @@ import * as assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { bufferedStreamToBuffer, bufferToReadable, bufferToStream, decodeBase64, encodeBase64, newWriteableBufferStream, readableToBuffer, streamToBuffer, VSBuffer } from 'vs/base/common/buffer'; import { peekStream } from 'vs/base/common/stream'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Buffer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #71993 - VSBuffer#toString returns numbers', () => { const data = new Uint8Array([1, 2, 3, 'h'.charCodeAt(0), 'i'.charCodeAt(0), 4, 5]).buffer; const buffer = VSBuffer.wrap(new Uint8Array(data, 3, 2)); diff --git a/src/vs/base/test/common/cancellation.test.ts b/src/vs/base/test/common/cancellation.test.ts index 5f7206f65fa..2e184c42267 100644 --- a/src/vs/base/test/common/cancellation.test.ts +++ b/src/vs/base/test/common/cancellation.test.ts @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('CancellationToken', function () { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('None', () => { assert.strictEqual(CancellationToken.None.isCancellationRequested, false); assert.strictEqual(typeof CancellationToken.None.onCancellationRequested, 'function'); @@ -35,7 +38,7 @@ suite('CancellationToken', function () { cancelCount += 1; } - source.token.onCancellationRequested(onCancel); + store.add(source.token.onCancellationRequested(onCancel)); source.cancel(); source.cancel(); @@ -48,15 +51,9 @@ suite('CancellationToken', function () { let count = 0; const source = new CancellationTokenSource(); - source.token.onCancellationRequested(function () { - count += 1; - }); - source.token.onCancellationRequested(function () { - count += 1; - }); - source.token.onCancellationRequested(function () { - count += 1; - }); + store.add(source.token.onCancellationRequested(() => count++)); + store.add(source.token.onCancellationRequested(() => count++)); + store.add(source.token.onCancellationRequested(() => count++)); source.cancel(); assert.strictEqual(count, 3); @@ -85,9 +82,7 @@ suite('CancellationToken', function () { let count = 0; const source = new CancellationTokenSource(); - source.token.onCancellationRequested(function () { - count += 1; - }); + store.add(source.token.onCancellationRequested(() => count++)); source.dispose(); source.cancel(); @@ -99,9 +94,7 @@ suite('CancellationToken', function () { let count = 0; const source = new CancellationTokenSource(); - source.token.onCancellationRequested(function () { - count += 1; - }); + store.add(source.token.onCancellationRequested(() => count++)); source.dispose(true); // source.cancel(); @@ -120,12 +113,15 @@ suite('CancellationToken', function () { const child = new CancellationTokenSource(parent.token); let count = 0; - child.token.onCancellationRequested(() => count += 1); + store.add(child.token.onCancellationRequested(() => count++)); parent.cancel(); assert.strictEqual(count, 1); assert.strictEqual(child.token.isCancellationRequested, true); assert.strictEqual(parent.token.isCancellationRequested, true); + + child.dispose(); + parent.dispose(); }); }); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 54b1e1f48c9..2231acac008 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -8,11 +8,11 @@ import { DeferredPromise, timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, DynamicListEventMultiplexer, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay, createEventDeliveryQueue } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable, DisposableTracker } from 'vs/base/common/lifecycle'; import { observableValue, transaction } from 'vs/base/common/observable'; import { MicrotaskDelay } from 'vs/base/common/symbols'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; -import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; namespace Samples { diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 343167bd57d..ae8627eb117 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -231,7 +231,7 @@ suite('No Leakage Utilities', () => { eventEmitter.event(() => { // noop }); - }); + }, false); }, e => e.message.indexOf('undisposed disposables') !== -1); }); @@ -239,7 +239,7 @@ suite('No Leakage Utilities', () => { assertThrows(() => { throwIfDisposablesAreLeaked(() => { new DisposableStore(); - }); + }, false); }, e => e.message.indexOf('undisposed disposables') !== -1); }); diff --git a/src/vs/base/test/common/paging.test.ts b/src/vs/base/test/common/paging.test.ts index d85f04fe6ca..f0ff02a4ab5 100644 --- a/src/vs/base/test/common/paging.test.ts +++ b/src/vs/base/test/common/paging.test.ts @@ -4,13 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { disposableTimeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { canceled, isCancellationError } from 'vs/base/common/errors'; +import { CancellationError, isCancellationError } from 'vs/base/common/errors'; import { IPager, PagedModel } from 'vs/base/common/paging'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function getPage(pageIndex: number, cancellationToken: CancellationToken): Promise { if (cancellationToken.isCancellationRequested) { - return Promise.reject(canceled()); + return Promise.reject(new CancellationError()); } return Promise.resolve([0, 1, 2, 3, 4].map(i => i + (pageIndex * 5))); @@ -30,6 +32,8 @@ class TestPager implements IPager { suite('PagedModel', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('isResolved', () => { const pager = new TestPager(); const model = new PagedModel(pager); @@ -116,11 +120,11 @@ suite('PagedModel', () => { test('cancellation works', function () { const pager = new TestPager((_, token) => new Promise((_, e) => { - token.onCancellationRequested(() => e(canceled())); + store.add(token.onCancellationRequested(() => e(new CancellationError()))); })); const model = new PagedModel(pager); - const tokenSource = new CancellationTokenSource(); + const tokenSource = store.add(new CancellationTokenSource()); const promise = model.resolve(5, tokenSource.token).then( () => assert(false), @@ -139,10 +143,10 @@ suite('PagedModel', () => { state = 'resolving'; return new Promise((_, e) => { - token.onCancellationRequested(() => { + store.add(token.onCancellationRequested(() => { state = 'idle'; - e(canceled()); - }); + e(new CancellationError()); + })); }); }); @@ -166,17 +170,17 @@ suite('PagedModel', () => { assert.strictEqual(state, 'resolving'); - setTimeout(() => { + store.add(disposableTimeout(() => { assert.strictEqual(state, 'resolving'); tokenSource1.cancel(); assert.strictEqual(state, 'resolving'); - setTimeout(() => { + store.add(disposableTimeout(() => { assert.strictEqual(state, 'resolving'); tokenSource2.cancel(); assert.strictEqual(state, 'idle'); - }, 10); - }, 10); + }, 10)); + }, 10)); return Promise.all([promise1, promise2]); }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 3301cdfe614..36c6873073f 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -3,12 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, numberComparator } from 'vs/base/common/arrays'; -import { SetMap, groupBy } from 'vs/base/common/collections'; -import { DisposableStore, IDisposable, IDisposableTracker, setDisposableTracker } from 'vs/base/common/lifecycle'; +import { DisposableStore, DisposableTracker, IDisposable, setDisposableTracker } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; -import { trim } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; export type ValueCallback = (value: T | Promise) => void; @@ -44,144 +41,6 @@ export async function assertThrowsAsync(block: () => any, message: string | Erro throw err; } -interface DisposableInfo { - value: IDisposable; - source: string | null; - parent: IDisposable | null; - isSingleton: boolean; -} - -export class DisposableTracker implements IDisposableTracker { - private readonly livingDisposables = new Map(); - - private getDisposableData(d: IDisposable) { - let val = this.livingDisposables.get(d); - if (!val) { - val = { parent: null, source: null, isSingleton: false, value: d }; - this.livingDisposables.set(d, val); - } - return val; - } - - trackDisposable(d: IDisposable): void { - const data = this.getDisposableData(d); - if (!data.source) { - data.source = - new Error().stack!; - } - } - - setParent(child: IDisposable, parent: IDisposable | null): void { - const data = this.getDisposableData(child); - data.parent = parent; - } - - markAsDisposed(x: IDisposable): void { - this.livingDisposables.delete(x); - } - - markAsSingleton(disposable: IDisposable): void { - this.getDisposableData(disposable).isSingleton = true; - } - - private getRootParent(data: DisposableInfo, cache: Map): DisposableInfo { - const cacheValue = cache.get(data); - if (cacheValue) { - return cacheValue; - } - - const result = data.parent ? this.getRootParent(this.getDisposableData(data.parent), cache) : data; - cache.set(data, result); - return result; - } - - getTrackedDisposables() { - const rootParentCache = new Map(); - - const leaking = [...this.livingDisposables.entries()] - .filter(([, v]) => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton) - .map(([k]) => k) - .flat(); - - return leaking; - } - - ensureNoLeakingDisposables() { - const rootParentCache = new Map(); - - const leakingObjects = [...this.livingDisposables.values()] - .filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton); - - if (leakingObjects.length === 0) { - return; - } - const leakingObjsSet = new Set(leakingObjects.map(o => o.value)); - - // Remove all objects that are a child of other leaking objects. Assumes there are no cycles. - const uncoveredLeakingObjs = leakingObjects.filter(l => { - return !(l.parent && leakingObjsSet.has(l.parent)); - }); - - if (uncoveredLeakingObjs.length === 0) { - throw new Error('There are cyclic diposable chains!'); - } - - function getStackTracePath(leaking: DisposableInfo): string[] { - function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) { - while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) { - array.shift(); - } - } - - const lines = leaking.source!.split('\n').map(p => trim(p.trim(), 'at ')).filter(l => l !== ''); - removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]); - return lines.reverse(); - } - - const stackTraceStarts = new SetMap(); - for (const leaking of uncoveredLeakingObjs) { - const stackTracePath = getStackTracePath(leaking); - for (let i = 0; i <= stackTracePath.length; i++) { - stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking); - } - } - - uncoveredLeakingObjs.sort(compareBy(l => getStackTracePath(l).length, numberComparator)); - - const maxReported = 10; - - let i = 0; - for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) { - i++; - const stackTracePath = getStackTracePath(leaking); - const stackTraceFormattedLines = []; - - for (let i = 0; i < stackTracePath.length; i++) { - let line = stackTracePath[i]; - const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n')); - line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`; - - const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n')); - const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v); - delete continuations[stackTracePath[i]]; - for (const [cont, set] of Object.entries(continuations)) { - stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`); - } - - stackTraceFormattedLines.unshift(line); - } - - console.error(`\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`); - } - - if (uncoveredLeakingObjs.length > maxReported) { - console.error(`\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`); - } - - throw new Error(`There are ${uncoveredLeakingObjs.length} undisposed disposables! (check test output)`); - } -} - /** * Use this function to ensure that all disposables are cleaned up at the end of each test in the current suite. * @@ -204,7 +63,11 @@ export function ensureNoDisposablesAreLeakedInTestSuite(): Pick void): void { +export function throwIfDisposablesAreLeaked(body: () => void, logToConsole = true): void { const tracker = new DisposableTracker(); setDisposableTracker(tracker); body(); setDisposableTracker(null); - tracker.ensureNoLeakingDisposables(); + computeLeakingDisposables(tracker, logToConsole); } export async function throwIfDisposablesAreLeakedAsync(body: () => Promise): Promise { @@ -230,5 +93,15 @@ export async function throwIfDisposablesAreLeakedAsync(body: () => Promise setDisposableTracker(tracker); await body(); setDisposableTracker(null); - tracker.ensureNoLeakingDisposables(); + computeLeakingDisposables(tracker); +} + +function computeLeakingDisposables(tracker: DisposableTracker, logToConsole = true) { + const result = tracker.computeLeakingDisposables(); + if (result) { + if (logToConsole) { + console.error(result.details); + } + throw new Error(`There are ${result.leaks.length} undisposed disposables!${result.details}`); + } } diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index ef6797629ca..0a898e2c7bc 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -10,28 +10,24 @@ import { FileAccess } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { Promises } from 'vs/base/node/pfs'; import { extract } from 'vs/base/node/zip'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; suite('Zip', () => { - let testDir: string; - - setup(() => { - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'zip'); - - return Promises.mkdir(testDir, { recursive: true }); - }); - - teardown(() => { - return Promises.rm(testDir); - }); + ensureNoDisposablesAreLeakedInTestSuite(); test('extract should handle directories', async () => { + const testDir = getRandomTestPath(tmpdir(), 'vsctests', 'zip'); + await Promises.mkdir(testDir, { recursive: true }); + const fixtures = FileAccess.asFileUri('vs/base/test/node/zip/fixtures').fsPath; const fixture = path.join(fixtures, 'extract.zip'); await createCancelablePromise(token => extract(fixture, testDir, {}, token)); const doesExist = await Promises.exists(path.join(testDir, 'extension')); assert(doesExist); + + await Promises.rm(testDir); }); }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 9fcc3ce6f10..5307987e657 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -14,7 +14,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath'; import { once } from 'vs/base/common/functional'; import { stripComments } from 'vs/base/common/json'; import { getPathLabel } from 'vs/base/common/labels'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join, posix } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; @@ -1051,10 +1051,12 @@ export class CodeApplication extends Disposable { // can talk to the first instance. Electron IPC does not work // across apps until `requestSingleInstance` APIs are adopted. - const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), { disableMarshalling: true }); + const disposables = this._register(new DisposableStore()); + + const launchChannel = ProxyChannel.fromService(accessor.get(ILaunchMainService), disposables, { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('launch', launchChannel); - const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), { disableMarshalling: true }); + const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsMainService), disposables, { disableMarshalling: true }); this.mainProcessNodeIpcServer.registerChannel('diagnostics', diagnosticsChannel); // Policies (main & shared process) @@ -1070,7 +1072,7 @@ export class CodeApplication extends Disposable { sharedProcessClient.then(client => client.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel)); // User Data Profiles - const userDataProfilesService = ProxyChannel.fromService(accessor.get(IUserDataProfilesMainService)); + const userDataProfilesService = ProxyChannel.fromService(accessor.get(IUserDataProfilesMainService), disposables); mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService); sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService)); @@ -1083,45 +1085,45 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('update', updateChannel); // Issues - const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService)); + const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService), disposables); mainProcessElectronServer.registerChannel('issue', issueChannel); // Encryption - const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService)); + const encryptionChannel = ProxyChannel.fromService(accessor.get(IEncryptionMainService), disposables); mainProcessElectronServer.registerChannel('encryption', encryptionChannel); // Signing - const signChannel = ProxyChannel.fromService(accessor.get(ISignService)); + const signChannel = ProxyChannel.fromService(accessor.get(ISignService), disposables); mainProcessElectronServer.registerChannel('sign', signChannel); // Keyboard Layout - const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService)); + const keyboardLayoutChannel = ProxyChannel.fromService(accessor.get(IKeyboardLayoutMainService), disposables); mainProcessElectronServer.registerChannel('keyboardLayout', keyboardLayoutChannel); // Native host (main & shared process) this.nativeHostMainService = accessor.get(INativeHostMainService); - const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService); + const nativeHostChannel = ProxyChannel.fromService(this.nativeHostMainService, disposables); mainProcessElectronServer.registerChannel('nativeHost', nativeHostChannel); sharedProcessClient.then(client => client.registerChannel('nativeHost', nativeHostChannel)); // Workspaces - const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService)); + const workspacesChannel = ProxyChannel.fromService(accessor.get(IWorkspacesService), disposables); mainProcessElectronServer.registerChannel('workspaces', workspacesChannel); // Menubar - const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService)); + const menubarChannel = ProxyChannel.fromService(accessor.get(IMenubarMainService), disposables); mainProcessElectronServer.registerChannel('menubar', menubarChannel); // URL handling - const urlChannel = ProxyChannel.fromService(accessor.get(IURLService)); + const urlChannel = ProxyChannel.fromService(accessor.get(IURLService), disposables); mainProcessElectronServer.registerChannel('url', urlChannel); // Extension URL Trust - const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService)); + const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService), disposables); mainProcessElectronServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel); // Webview Manager - const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService)); + const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService), disposables); mainProcessElectronServer.registerChannel('webview', webviewChannel); // Storage (main & shared process) @@ -1134,11 +1136,11 @@ export class CodeApplication extends Disposable { sharedProcessClient.then(client => client.registerChannel('profileStorageListener', profileStorageListener)); // Terminal - const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService)); + const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService), disposables); mainProcessElectronServer.registerChannel(TerminalIpcChannels.LocalPty, ptyHostChannel); // External Terminal - const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService)); + const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService), disposables); mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel); // Logger @@ -1151,11 +1153,11 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('extensionhostdebugservice', electronExtensionHostDebugBroadcastChannel); // Extension Host Starter - const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter)); + const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter), disposables); mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel); // Utility Process Worker - const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService)); + const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService), disposables); mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel); } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 7bdb1e1a459..7221594d2af 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -179,10 +179,6 @@ class CodeMain { const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // Use FileUserDataProvider for user data to - // enable atomic read / write operations. - fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, logService)); - // URI Identity const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); @@ -196,6 +192,10 @@ class CodeMain { const userDataProfilesMainService = new UserDataProfilesMainService(stateService, uriIdentityService, environmentMainService, fileService, logService); services.set(IUserDataProfilesMainService, userDataProfilesMainService); + // Use FileUserDataProvider for user data to + // enable atomic read / write operations. + fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, userDataProfilesMainService, uriIdentityService, logService)); + // Policy const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(logService, productService.win32RegValueName)) : environmentMainService.policyFile ? disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)) diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index b97aa7a0be8..85daa51a6e1 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -146,10 +146,6 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // Use FileUserDataProvider for user data to - // enable atomic read / write operations. - fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, logService)); - // Uri Identity const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); @@ -159,6 +155,10 @@ class CliMain extends Disposable { const userDataProfilesService = new UserDataProfilesReadonlyService(stateService, uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); + // Use FileUserDataProvider for user data to + // enable atomic read / write operations. + fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService)); + // Policy const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(logService, productService.win32RegValueName)) : environmentService.policyFile ? this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)) diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index a81bedb9ab4..48791c1b7bb 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -7,7 +7,7 @@ import { hostname, release } from 'os'; import { MessagePortMain, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { firstOrDefault } from 'vs/base/common/arrays'; @@ -60,9 +60,9 @@ import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryServi import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { UserDataAutoSyncChannel, UserDataSyncAccountServiceChannel, UserDataSyncMachinesServiceChannel, UserDataSyncStoreManagementServiceChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; @@ -223,6 +223,14 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // URI Identity + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + + // User Data Profiles + const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home).with({ scheme: environmentService.userRoamingDataHome.scheme }), mainProcessService.getChannel('userDataProfiles'))); + services.set(IUserDataProfilesService, userDataProfilesService); + const userDataFileSystemProvider = this._register(new FileUserDataProvider( Schemas.file, // Specifically for user data, use the disk file system provider @@ -231,14 +239,12 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // processes, we want a single process handling these operations. this._register(new DiskFileSystemProviderClient(mainProcessService.getChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME), { pathCaseSensitive: isLinux })), Schemas.vscodeUserData, + userDataProfilesService, + uriIdentityService, logService )); fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); - // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home).with({ scheme: environmentService.userRoamingDataHome.scheme }), mainProcessService.getChannel('userDataProfiles'))); - services.set(IUserDataProfilesService, userDataProfilesService); - // Configuration const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); @@ -254,10 +260,6 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { storageService.initialize() ]); - // URI Identity - const uriIdentityService = new UriIdentityService(fileService); - services.set(IUriIdentityService, uriIdentityService); - // Request const requestService = new RequestChannelClient(mainProcessService.getChannel('request')); services.set(IRequestService, requestService); @@ -340,7 +342,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService, undefined, true)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService, undefined, true)); services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService, undefined, true)); - services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService, undefined, false /* Eagerly cleans up old backups */)); + services.set(IUserDataSyncLocalStoreService, new SyncDescriptor(UserDataSyncLocalStoreService, undefined, false /* Eagerly cleans up old backups */)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService, undefined, true)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService, undefined, false /* Initializes the Sync State */)); services.set(IUserDataProfileStorageService, new SyncDescriptor(NativeUserDataProfileStorageService, undefined, true)); @@ -367,16 +369,18 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { private initChannels(accessor: ServicesAccessor): void { + const disposables = this._register(new DisposableStore()); + // Extensions Management const channel = new ExtensionManagementChannel(accessor.get(IExtensionManagementService), () => null); this.server.registerChannel('extensions', channel); // Language Packs - const languagePacksChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService)); + const languagePacksChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); this.server.registerChannel('languagePacks', languagePacksChannel); // Diagnostics - const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService)); + const diagnosticsChannel = ProxyChannel.fromService(accessor.get(IDiagnosticsService), disposables); this.server.registerChannel('diagnostics', diagnosticsChannel); // Extension Tips @@ -384,11 +388,11 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { this.server.registerChannel('extensionTipsService', extensionTipsChannel); // Checksum - const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService)); + const checksumChannel = ProxyChannel.fromService(accessor.get(IChecksumService), disposables); this.server.registerChannel('checksum', checksumChannel); // Profiling - const profilingChannel = ProxyChannel.fromService(accessor.get(IV8InspectProfilingService)); + const profilingChannel = ProxyChannel.fromService(accessor.get(IV8InspectProfilingService), disposables); this.server.registerChannel('v8InspectProfiling', profilingChannel); // Settings Sync @@ -396,7 +400,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { this.server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel); // Custom Endpoint Telemetry - const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService)); + const customEndpointTelemetryChannel = ProxyChannel.fromService(accessor.get(ICustomEndpointTelemetryService), disposables); this.server.registerChannel('customEndpointTelemetry', customEndpointTelemetryChannel); const userDataSyncAccountChannel = new UserDataSyncAccountServiceChannel(accessor.get(IUserDataSyncAccountService)); @@ -412,12 +416,14 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); this.server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); + this.server.registerChannel('IUserDataSyncResourceProviderService', ProxyChannel.fromService(accessor.get(IUserDataSyncResourceProviderService), disposables)); + // Tunnel - const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService)); + const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService), disposables); this.server.registerChannel(ipcSharedProcessTunnelChannelName, sharedProcessTunnelChannel); // Remote Tunnel - const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService)); + const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService), disposables); this.server.registerChannel('remoteTunnel', remoteTunnelChannel); } diff --git a/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts b/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts new file mode 100644 index 00000000000..08ed249b8b9 --- /dev/null +++ b/src/vs/editor/browser/diff/testDiffProviderFactoryService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { toDisposable } from 'vs/base/common/lifecycle'; +import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { linesDiffComputers } from 'vs/editor/common/diff/linesDiffComputers'; +import { ITextModel } from 'vs/editor/common/model'; +import { Event } from 'vs/base/common/event'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; + +export class TestDiffProviderFactoryService implements IDiffProviderFactoryService { + declare readonly _serviceBrand: undefined; + createDiffProvider(): IDocumentDiffProvider { + return new SyncDocumentDiffProvider(); + } +} + +export class SyncDocumentDiffProvider implements IDocumentDiffProvider { + computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions, cancellationToken: CancellationToken): Promise { + const result = linesDiffComputers.getDefault().computeDiff(original.getLinesContent(), modified.getLinesContent(), options); + return Promise.resolve({ + changes: result.changes, + quitEarly: result.hitTimeout, + identical: original.getValue() === modified.getValue(), + moves: result.moves, + }); + } + + onDidChange: Event = () => toDisposable(() => { }); +} diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 31e38a56c4d..dc3ecf70528 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -347,7 +347,7 @@ export class DecorationsOverviewRuler extends ViewPart { } const decorations = this._context.viewModel.getAllOverviewRulerDecorations(this._context.theme); - decorations.sort(OverviewRulerDecorationsGroup.cmp); + decorations.sort(OverviewRulerDecorationsGroup.compareByRenderingProps); if (this._actualShouldRender === ShouldRenderValue.Maybe && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) { this._actualShouldRender = ShouldRenderValue.Needed; diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 7eb51c59d65..9ccc307b2cd 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -25,7 +25,9 @@ export class ToggleCollapseUnchangedRegions extends Action2 { title: { value: localize('toggleCollapseUnchangedRegions', "Toggle Collapse Unchanged Regions"), original: 'Toggle Collapse Unchanged Regions' }, icon: Codicon.map, toggled: ContextKeyExpr.has('config.diffEditor.hideUnchangedRegions.enabled'), + precondition: ContextKeyExpr.has('isInDiffEditor'), menu: { + when: ContextKeyExpr.has('isInDiffEditor'), id: MenuId.EditorTitle, order: 22, group: 'navigation', @@ -47,6 +49,7 @@ export class ToggleShowMovedCodeBlocks extends Action2 { super({ id: 'diffEditor.toggleShowMovedCodeBlocks', title: { value: localize('toggleShowMovedCodeBlocks', "Toggle Show Moved Code Blocks"), original: 'Toggle Show Moved Code Blocks' }, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -64,6 +67,7 @@ export class ToggleUseInlineViewWhenSpaceIsLimited extends Action2 { super({ id: 'diffEditor.toggleUseInlineViewWhenSpaceIsLimited', title: { value: localize('toggleUseInlineViewWhenSpaceIsLimited', "Toggle Use Inline View When Space Is Limited"), original: 'Toggle Use Inline View When Space Is Limited' }, + precondition: ContextKeyExpr.has('isInDiffEditor'), }); } @@ -81,10 +85,14 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: new ToggleUseInlineViewWhenSpaceIsLimited().desc.id, title: localize('useInlineViewWhenSpaceIsLimited', "Use Inline View When Space Is Limited"), toggled: ContextKeyExpr.has('config.diffEditor.useInlineViewWhenSpaceIsLimited'), + precondition: ContextKeyExpr.has('isInDiffEditor'), }, order: 11, group: '1_diff', - when: EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, + when: ContextKeyExpr.and( + EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, + ContextKeyExpr.has('isInDiffEditor'), + ), }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -93,9 +101,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { title: localize('showMoves', "Show Moved Code Blocks"), icon: Codicon.move, toggled: ContextKeyEqualsExpr.create('config.diffEditor.experimental.showMoves', true), + precondition: ContextKeyExpr.has('isInDiffEditor'), }, order: 10, group: '1_diff', + when: ContextKeyExpr.has('isInDiffEditor'), }); const diffEditorCategory: ILocalizedString = { @@ -232,6 +242,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: AccessibleDiffViewerNext.id, title: localize('Open Accessible Diff Viewer', "Open Accessible Diff Viewer"), + precondition: ContextKeyExpr.has('isInDiffEditor'), }, order: 10, group: '2_diff', diff --git a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts index c67fd8822af..edaf0c6e099 100644 --- a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts +++ b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts @@ -316,24 +316,24 @@ class MovedBlockOverlayWidget extends ViewZoneOverlayWidget { 'codeMovedToWithChanges', 'Code moved with changes to line {0}-{1}', this._move.lineRangeMapping.modified.startLineNumber, - this._move.lineRangeMapping.modified.endLineNumberExclusive + this._move.lineRangeMapping.modified.endLineNumberExclusive - 1, ) : localize( 'codeMovedFromWithChanges', 'Code moved with changes from line {0}-{1}', this._move.lineRangeMapping.original.startLineNumber, - this._move.lineRangeMapping.original.endLineNumberExclusive + this._move.lineRangeMapping.original.endLineNumberExclusive - 1, ); } else { text = this._kind === 'original' ? localize( 'codeMovedTo', 'Code moved to line {0}-{1}', this._move.lineRangeMapping.modified.startLineNumber, - this._move.lineRangeMapping.modified.endLineNumberExclusive + this._move.lineRangeMapping.modified.endLineNumberExclusive - 1, ) : localize( 'codeMovedFrom', 'Code moved from line {0}-{1}', this._move.lineRangeMapping.original.startLineNumber, - this._move.lineRangeMapping.original.endLineNumberExclusive + this._move.lineRangeMapping.original.endLineNumberExclusive - 1, ); } diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index 6e52932ef8e..d81f5cf2ebe 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -286,3 +286,14 @@ .monaco-diff-editor .diffViewport:active { background: var(--vscode-scrollbarSlider-activeBackground); } + +.monaco-editor .diagonal-fill { + background-image: linear-gradient( + -45deg, + var(--vscode-diffEditor-diagonalFill) 12.5%, + #0000 12.5%, #0000 50%, + var(--vscode-diffEditor-diagonalFill) 50%, var(--vscode-diffEditor-diagonalFill) 62.5%, + #0000 62.5%, #0000 100% + ); + background-size: 8px 8px; +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f138511c9a4..d5938f90014 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2038,6 +2038,11 @@ export interface IEditorHoverOptions { * Defaults to true. */ sticky?: boolean; + /** + * Controls how long the hover is visible after you hovered out of it. + * Require sticky setting to be true. + */ + hidingDelay?: number; /** * Should the hover be shown above the line if possible? * Defaults to false. @@ -2056,6 +2061,7 @@ class EditorHover extends BaseEditorOption { + /** + * Pending comment threads. + */ + readonly pending: PendingCommentThread[]; + /** * Added comment threads. */ diff --git a/src/vs/editor/common/services/markerDecorationsService.ts b/src/vs/editor/common/services/markerDecorationsService.ts index c6397b71e0a..de2c54e9e01 100644 --- a/src/vs/editor/common/services/markerDecorationsService.ts +++ b/src/vs/editor/common/services/markerDecorationsService.ts @@ -16,7 +16,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markerDecorations'; import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; -import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; +import { minimapInfo, minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; import { BidirectionalMap, ResourceMap } from 'vs/base/common/map'; import { diffSets } from 'vs/base/common/collections'; @@ -208,6 +208,15 @@ class MarkerDecorations extends Disposable { } zIndex = 0; break; + case MarkerSeverity.Info: + className = ClassName.EditorInfoDecoration; + color = themeColorFromId(overviewRulerInfo); + zIndex = 10; + minimap = { + color: themeColorFromId(minimapInfo), + position: MinimapPosition.Inline + }; + break; case MarkerSeverity.Warning: className = ClassName.EditorWarningDecoration; color = themeColorFromId(overviewRulerWarning); @@ -217,11 +226,6 @@ class MarkerDecorations extends Disposable { position: MinimapPosition.Inline }; break; - case MarkerSeverity.Info: - className = ClassName.EditorInfoDecoration; - color = themeColorFromId(overviewRulerInfo); - zIndex = 10; - break; case MarkerSeverity.Error: default: className = ClassName.EditorErrorDecoration; diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 4e4b4d3032f..335816d0dcc 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -3,20 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as arrays from 'vs/base/common/arrays'; import { IScrollPosition, Scrollable } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; -import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, ICursorSimpleModel, PartialCursorState } from 'vs/editor/common/cursorCommon'; -import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; -import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, ICursorSimpleModel, PartialCursorState } from 'vs/editor/common/cursorCommon'; +import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; -import { BracketGuideOptions, IActiveIndentGuideInfo, IndentGuide } from 'vs/editor/common/textModelGuides'; import { EditorTheme } from 'vs/editor/common/editorTheme'; -import { VerticalRevealType } from 'vs/editor/common/viewEvents'; +import { EndOfLinePreference, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; import { ILineBreaksComputer, InjectedText } from 'vs/editor/common/modelLineProjectionData'; +import { BracketGuideOptions, IActiveIndentGuideInfo, IndentGuide } from 'vs/editor/common/textModelGuides'; +import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; +import { VerticalRevealType } from 'vs/editor/common/viewEvents'; export interface IViewModel extends ICursorSimpleModel { @@ -433,7 +434,7 @@ export class OverviewRulerDecorationsGroup { public readonly data: number[] ) { } - public static cmp(a: OverviewRulerDecorationsGroup, b: OverviewRulerDecorationsGroup): number { + public static compareByRenderingProps(a: OverviewRulerDecorationsGroup, b: OverviewRulerDecorationsGroup): number { if (a.zIndex === b.zIndex) { if (a.color < b.color) { return -1; @@ -446,15 +447,15 @@ export class OverviewRulerDecorationsGroup { return a.zIndex - b.zIndex; } + public static equals(a: OverviewRulerDecorationsGroup, b: OverviewRulerDecorationsGroup): boolean { + return ( + a.color === b.color + && a.zIndex === b.zIndex + && arrays.equals(a.data, b.data) + ); + } + public static equalsArr(a: OverviewRulerDecorationsGroup[], b: OverviewRulerDecorationsGroup[]): boolean { - if (a.length !== b.length) { - return false; - } - for (let i = 0, len = a.length; i < len; i++) { - if (OverviewRulerDecorationsGroup.cmp(a[i], b[i]) !== 0) { - return false; - } - } - return true; + return arrays.equals(a, b, OverviewRulerDecorationsGroup.equals); } } diff --git a/src/vs/editor/contrib/caretOperations/test/browser/moveCarretCommand.test.ts b/src/vs/editor/contrib/caretOperations/test/browser/moveCarretCommand.test.ts index b30aa76ca63..dc1d3133000 100644 --- a/src/vs/editor/contrib/caretOperations/test/browser/moveCarretCommand.test.ts +++ b/src/vs/editor/contrib/caretOperations/test/browser/moveCarretCommand.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { MoveCaretCommand } from 'vs/editor/contrib/caretOperations/browser/moveCaretCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; @@ -18,6 +19,8 @@ function testMoveCaretRightCommand(lines: string[], selection: Selection, expect suite('Editor Contrib - Move Caret Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('move selection to left', function () { testMoveCaretLeftCommand( [ 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 09699fd6d12..ac919bb6192 100644 --- a/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import * as languages from 'vs/editor/common/languages'; @@ -105,6 +106,8 @@ suite('CodeAction', () => { disposables.clear(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('CodeActions are sorted by type, #38623', async () => { const provider = staticCodeActionProvider( @@ -130,7 +133,7 @@ suite('CodeAction', () => { new CodeActionItem(testData.tsLint.abc, provider) ]; - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 6); assert.deepStrictEqual(actions, expected); }); @@ -145,20 +148,20 @@ suite('CodeAction', () => { disposables.add(registry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 2); assert.strictEqual(actions[0].action.title, 'a'); assert.strictEqual(actions[1].action.title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a.b.c') } }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a.b.c') } }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 0); } }); @@ -177,7 +180,7 @@ suite('CodeAction', () => { disposables.add(registry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); }); @@ -191,13 +194,13 @@ suite('CodeAction', () => { disposables.add(registry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'b'); } { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None); + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); } @@ -213,13 +216,13 @@ suite('CodeAction', () => { disposables.add(registry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction, filter: { include: CodeActionKind.Source.append('test'), excludes: [CodeActionKind.Source], includeSourceActions: true, } - }, Progress.None, CancellationToken.None); + }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'b'); } @@ -250,12 +253,12 @@ suite('CodeAction', () => { })); { - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor, filter: { include: baseType, excludes: [subType], } - }, Progress.None, CancellationToken.None); + }, Progress.None, CancellationToken.None)); assert.strictEqual(didInvoke, false); assert.strictEqual(actions.length, 1); assert.strictEqual(actions[0].action.title, 'a'); @@ -275,12 +278,12 @@ suite('CodeAction', () => { disposables.add(registry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(registry, model, new Range(1, 1, 2, 1), { + const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor, filter: { include: CodeActionKind.QuickFix } - }, Progress.None, CancellationToken.None); + }, Progress.None, CancellationToken.None)); assert.strictEqual(actions.length, 0); assert.strictEqual(wasInvoked, false); }); diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index da810c9a099..f260a892cca 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -134,7 +134,7 @@ export class CodeLensContribution implements IEditorContribution { return; } - if (!this._editor.getOption(EditorOption.codeLens)) { + if (!this._editor.getOption(EditorOption.codeLens) || model.isTooLargeForTokenization()) { return; } diff --git a/src/vs/editor/contrib/comment/test/browser/blockCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/browser/blockCommentCommand.test.ts index 51e71c6aab3..1888af3a963 100644 --- a/src/vs/editor/contrib/comment/test/browser/blockCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/browser/blockCommentCommand.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -31,6 +32,8 @@ function testBlockCommentCommand(lines: string[], selection: Selection, expected suite('Editor Contrib - Block Comment Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('empty selection wraps itself', function () { testBlockCommentCommand( [ diff --git a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts index 7f9bea14db9..ff394a835bb 100644 --- a/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts +++ b/src/vs/editor/contrib/comment/test/browser/lineCommentCommand.test.ts @@ -5,18 +5,19 @@ import * as assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; -import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; import { ColorId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { CommentRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILinePreflightData, IPreflightData, ISimpleModel, LineCommentCommand, Type } from 'vs/editor/contrib/comment/browser/lineCommentCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; function createTestCommandHelper(commentsConfig: CommentRule, commandFactory: (accessor: ServicesAccessor, selection: Selection) => ICommand): (lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection) => void { return (lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection) => { @@ -35,6 +36,8 @@ function createTestCommandHelper(commentsConfig: CommentRule, commandFactory: (a suite('Editor Contrib - Line Comment Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const testLineCommentCommand = createTestCommandHelper( { lineComment: '!@#', blockComment: [''] }, (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, true) @@ -99,6 +102,7 @@ suite('Editor Contrib - Line Comment Command', () => { } test('_analyzeLines', () => { + const disposable = new DisposableStore(); let r: IPreflightData; r = LineCommentCommand._analyzeLines(Type.Toggle, true, createSimpleModel([ @@ -106,7 +110,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' ', ' c', '\t\td' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, new TestLanguageConfigurationService()); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService())); if (!r.supported) { throw new Error(`unexpected`); } @@ -137,7 +141,7 @@ suite('Editor Contrib - Line Comment Command', () => { ' rem ', ' !@# c', '\t\t!@#d' - ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, new TestLanguageConfigurationService()); + ]), createBasicLinePreflightData(['//', 'rem', '!@#', '!@#']), 1, true, false, disposable.add(new TestLanguageConfigurationService())); if (!r.supported) { throw new Error(`unexpected`); } @@ -167,6 +171,8 @@ suite('Editor Contrib - Line Comment Command', () => { assert.strictEqual(r.lines[1].commentStrLength, 4); assert.strictEqual(r.lines[2].commentStrLength, 4); assert.strictEqual(r.lines[3].commentStrLength, 3); + + disposable.dispose(); }); test('_normalizeInsertionPoint', () => { @@ -678,101 +684,105 @@ suite('Editor Contrib - Line Comment Command', () => { new Selection(1, 1, 1, 1) ); }); +}); - suite('ignoreEmptyLines false', () => { +suite('ignoreEmptyLines false', () => { - const testLineCommentCommand = createTestCommandHelper( - { lineComment: '!@#', blockComment: [''] }, - (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, false) + ensureNoDisposablesAreLeakedInTestSuite(); + + const testLineCommentCommand = createTestCommandHelper( + { lineComment: '!@#', blockComment: [''] }, + (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, false) + ); + + test('does not ignore whitespace lines', () => { + testLineCommentCommand( + [ + '\tsome text', + '\t ', + '', + '\tsome more text' + ], + new Selection(4, 2, 1, 1), + [ + '!@# \tsome text', + '!@# \t ', + '!@# ', + '!@# \tsome more text' + ], + new Selection(4, 6, 1, 5) ); + }); - test('does not ignore whitespace lines', () => { - testLineCommentCommand( - [ - '\tsome text', - '\t ', - '', - '\tsome more text' - ], - new Selection(4, 2, 1, 1), - [ - '!@# \tsome text', - '!@# \t ', - '!@# ', - '!@# \tsome more text' - ], - new Selection(4, 6, 1, 5) - ); - }); + test('removes its own', function () { + testLineCommentCommand( + [ + '\t!@# some text', + '\t ', + '\t\t!@# some more text' + ], + new Selection(3, 2, 1, 1), + [ + '\tsome text', + '\t ', + '\t\tsome more text' + ], + new Selection(3, 2, 1, 1) + ); + }); - test('removes its own', function () { - testLineCommentCommand( - [ - '\t!@# some text', - '\t ', - '\t\t!@# some more text' - ], - new Selection(3, 2, 1, 1), - [ - '\tsome text', - '\t ', - '\t\tsome more text' - ], - new Selection(3, 2, 1, 1) - ); - }); + test('works in only whitespace', function () { + testLineCommentCommand( + [ + '\t ', + '\t', + '\t\tsome more text' + ], + new Selection(3, 1, 1, 1), + [ + '\t!@# ', + '\t!@# ', + '\t\tsome more text' + ], + new Selection(3, 1, 1, 1) + ); + }); - test('works in only whitespace', function () { - testLineCommentCommand( - [ - '\t ', - '\t', - '\t\tsome more text' - ], - new Selection(3, 1, 1, 1), - [ - '\t!@# ', - '\t!@# ', - '\t\tsome more text' - ], - new Selection(3, 1, 1, 1) - ); - }); + test('comments single line', function () { + testLineCommentCommand( + [ + 'some text', + '\tsome more text' + ], + new Selection(1, 1, 1, 1), + [ + '!@# some text', + '\tsome more text' + ], + new Selection(1, 5, 1, 5) + ); + }); - test('comments single line', function () { - testLineCommentCommand( - [ - 'some text', - '\tsome more text' - ], - new Selection(1, 1, 1, 1), - [ - '!@# some text', - '\tsome more text' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('detects indentation', function () { - testLineCommentCommand( - [ - '\tsome text', - '\tsome more text' - ], - new Selection(2, 2, 1, 1), - [ - '\t!@# some text', - '\t!@# some more text' - ], - new Selection(2, 2, 1, 1) - ); - }); + test('detects indentation', function () { + testLineCommentCommand( + [ + '\tsome text', + '\tsome more text' + ], + new Selection(2, 2, 1, 1), + [ + '\t!@# some text', + '\t!@# some more text' + ], + new Selection(2, 2, 1, 1) + ); }); }); suite('Editor Contrib - Line Comment As Block Comment', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const testLineCommentCommand = createTestCommandHelper( { lineComment: '', blockComment: ['(', ')'] }, (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, true) @@ -884,6 +894,8 @@ suite('Editor Contrib - Line Comment As Block Comment', () => { suite('Editor Contrib - Line Comment As Block Comment 2', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const testLineCommentCommand = createTestCommandHelper( { lineComment: null, blockComment: [''] }, (accessor, sel) => new LineCommentCommand(accessor.get(ILanguageConfigurationService), sel, 4, Type.Toggle, true, true) @@ -1080,6 +1092,8 @@ suite('Editor Contrib - Line Comment As Block Comment 2', () => { suite('Editor Contrib - Line Comment in mixed modes', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const OUTER_LANGUAGE_ID = 'outerMode'; const INNER_LANGUAGE_ID = 'innerMode'; diff --git a/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts b/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts index dbeae91158f..3cecf3ee8eb 100644 --- a/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts +++ b/src/vs/editor/contrib/cursorUndo/test/browser/cursorUndo.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands, CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; @@ -12,6 +13,8 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('FindController', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const cursorUndoAction = new CursorUndo(); test('issue #82535: Edge case with cursorUndo', () => { diff --git a/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts b/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts index 36d6d71712d..cc2a8d0f93e 100644 --- a/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts +++ b/src/vs/editor/contrib/editorState/test/browser/editorState.test.ts @@ -5,11 +5,12 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { CodeEditorStateFlag, EditorState } from 'vs/editor/contrib/editorState/browser/editorState'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; +import { CodeEditorStateFlag, EditorState } from 'vs/editor/contrib/editorState/browser/editorState'; interface IStubEditorState { model?: { uri?: URI; version?: number }; @@ -20,6 +21,8 @@ interface IStubEditorState { suite('Editor Core - Editor State', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const allFlags = ( CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection diff --git a/src/vs/editor/contrib/find/test/browser/find.test.ts b/src/vs/editor/contrib/find/test/browser/find.test.ts index f63f9e991da..466c39baf1e 100644 --- a/src/vs/editor/contrib/find/test/browser/find.test.ts +++ b/src/vs/editor/contrib/find/test/browser/find.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { getSelectionSearchString } from 'vs/editor/contrib/find/browser/findController'; @@ -12,6 +13,8 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('Find', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('search string at position', () => { withTestCodeEditor([ 'ABC DEF', diff --git a/src/vs/editor/contrib/find/test/browser/findController.test.ts b/src/vs/editor/contrib/find/test/browser/findController.test.ts index 9db7f06f6ce..96505ecc752 100644 --- a/src/vs/editor/contrib/find/test/browser/findController.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findController.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { Delayer } from 'vs/base/common/async'; import * as platform from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction } from 'vs/editor/browser/editorExtensions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -63,6 +64,9 @@ function executeAction(instantiationService: IInstantiationService, editor: ICod } suite('FindController', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + let clipboardState = ''; const serviceCollection = new ServiceCollection(); serviceCollection.set(IStorageService, new InMemoryStorageService()); @@ -476,6 +480,9 @@ suite('FindController', () => { }); suite('FindController query options persistence', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + const serviceCollection = new ServiceCollection(); const storageService = new InMemoryStorageService(); storageService.store('editor.isRegex', false, StorageScope.WORKSPACE, StorageTarget.USER); diff --git a/src/vs/editor/contrib/find/test/browser/findModel.test.ts b/src/vs/editor/contrib/find/test/browser/findModel.test.ts index a7d58f958b9..9b3450c2ff6 100644 --- a/src/vs/editor/contrib/find/test/browser/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/browser/findModel.test.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; @@ -16,6 +18,18 @@ import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('FindModel', () => { + let disposables: DisposableStore; + + setup(() => { + disposables = new DisposableStore(); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + function findTest(testName: string, callback: (editor: IActiveCodeEditor) => void): void { test(testName, () => { const textArr = [ @@ -87,8 +101,8 @@ suite('FindModel', () => { findTest('incremental find from beginning of file', (editor) => { editor.setPosition({ lineNumber: 1, column: 1 }); - const findState = new FindReplaceState(); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findState = disposables.add(new FindReplaceState()); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); // simulate typing the search string findState.change({ searchString: 'H' }, true); @@ -236,9 +250,9 @@ suite('FindModel', () => { }); findTest('find model removes its decorations', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assert.strictEqual(findState.matchesCount, 5); assertFindState( @@ -266,9 +280,9 @@ suite('FindModel', () => { }); findTest('find model updates state matchesCount', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assert.strictEqual(findState.matchesCount, 5); assertFindState( @@ -298,9 +312,9 @@ suite('FindModel', () => { }); findTest('find model reacts to position change', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -351,9 +365,9 @@ suite('FindModel', () => { }); findTest('find model next', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -437,9 +451,9 @@ suite('FindModel', () => { }); findTest('find model next stays in scope', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -489,9 +503,9 @@ suite('FindModel', () => { }); findTest('multi-selection find model next stays in scope (overlap)', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 8, 2), new Range(8, 1, 9, 1)] }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -541,9 +555,9 @@ suite('FindModel', () => { }); findTest('multi-selection find model next stays in scope', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', matchCase: true, wholeWord: false, searchScope: [new Range(6, 1, 7, 38), new Range(9, 3, 9, 38)] }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -614,9 +628,9 @@ suite('FindModel', () => { }); findTest('find model prev', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -700,9 +714,9 @@ suite('FindModel', () => { }); findTest('find model prev stays in scope', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 9, 1)] }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -752,9 +766,9 @@ suite('FindModel', () => { }); findTest('find model next/prev with no matches', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'helloo', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -784,9 +798,9 @@ suite('FindModel', () => { }); findTest('find model next/prev respects cursor position', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -833,9 +847,9 @@ suite('FindModel', () => { }); findTest('find ^', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -904,9 +918,9 @@ suite('FindModel', () => { }); findTest('find $', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '$', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -996,9 +1010,9 @@ suite('FindModel', () => { }); findTest('find next ^$', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^$', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1048,9 +1062,9 @@ suite('FindModel', () => { }); findTest('find .*', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '.*', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1077,9 +1091,9 @@ suite('FindModel', () => { }); findTest('find next ^.*$', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^.*$', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1148,9 +1162,9 @@ suite('FindModel', () => { }); findTest('find prev ^.*$', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^.*$', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1219,9 +1233,9 @@ suite('FindModel', () => { }); findTest('find prev ^$', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^$', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1271,9 +1285,9 @@ suite('FindModel', () => { }); findTest('replace hello', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1367,9 +1381,9 @@ suite('FindModel', () => { }); findTest('replace bla', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'bla', replaceString: 'ciao' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1432,9 +1446,9 @@ suite('FindModel', () => { }); findTest('replaceAll hello', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1480,9 +1494,9 @@ suite('FindModel', () => { }); findTest('replaceAll two spaces with one space', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: ' ', replaceString: ' ' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1522,9 +1536,9 @@ suite('FindModel', () => { }); findTest('replaceAll bla', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'bla', replaceString: 'ciao' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1551,9 +1565,9 @@ suite('FindModel', () => { }); findTest('replaceAll bla with \\t\\n', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'bla', replaceString: '<\\n\\t>', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1583,9 +1597,9 @@ suite('FindModel', () => { }); findTest('issue #3516: "replace all" moves page/cursor/focus/scroll to the place of the last replacement', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'include', replaceString: 'bar' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1613,9 +1627,9 @@ suite('FindModel', () => { }); findTest('listens to model content changes', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1642,9 +1656,9 @@ suite('FindModel', () => { }); findTest('selectAllMatches', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1684,9 +1698,9 @@ suite('FindModel', () => { }); findTest('issue #14143 selectAllMatches should maintain primary cursor if feasible', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1730,9 +1744,9 @@ suite('FindModel', () => { }); findTest('issue #1914: NPE when there is only one find match', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'cool.h' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1768,9 +1782,9 @@ suite('FindModel', () => { }); findTest('replace when search string has look ahed regex', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello(?=\\sworld)', replaceString: 'hi', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1834,9 +1848,9 @@ suite('FindModel', () => { }); findTest('replace when search string has look ahed regex and cursor is at the last find match', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello(?=\\sworld)', replaceString: 'hi', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); editor.trigger('mouse', CoreNavigationCommands.MoveTo.id, { position: new Position(8, 14) @@ -1905,9 +1919,9 @@ suite('FindModel', () => { }); findTest('replaceAll when search string has look ahed regex', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello(?=\\sworld)', replaceString: 'hi', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -1938,9 +1952,9 @@ suite('FindModel', () => { }); findTest('replace when search string has look ahed regex and replace string has capturing groups', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hel(lo)(?=\\sworld)', replaceString: 'hi$1', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2004,9 +2018,9 @@ suite('FindModel', () => { }); findTest('replaceAll when search string has look ahed regex and replace string has capturing groups', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'wo(rl)d(?=.*;$)', replaceString: 'gi$1', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2039,9 +2053,9 @@ suite('FindModel', () => { }); findTest('replaceAll when search string is multiline and has look ahed regex and replace string has capturing groups', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'wo(rl)d(.*;\\n)(?=.*hello)', replaceString: 'gi$1$2', isRegex: true, matchCase: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2070,9 +2084,9 @@ suite('FindModel', () => { }); findTest('replaceAll preserving case', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: 'goodbye', isRegex: false, matchCase: false, preserveCase: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2106,9 +2120,9 @@ suite('FindModel', () => { }); findTest('issue #18711 replaceAll with empty string', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', replaceString: '', wholeWord: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2143,9 +2157,9 @@ suite('FindModel', () => { initialText += 'line' + i + '\n'; } editor!.getModel()!.setValue(initialText); - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: '^', replaceString: 'a ', isRegex: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); findModel.replaceAll(); @@ -2161,9 +2175,9 @@ suite('FindModel', () => { }); findTest('issue #19740 Find and replace capture group/backreference inserts `undefined` instead of empty string', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello(z)?', replaceString: 'hi$1', isRegex: true, matchCase: true }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2192,9 +2206,9 @@ suite('FindModel', () => { }); findTest('issue #27083. search scope works even if it is a single line', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', wholeWord: true, searchScope: [new Range(7, 1, 8, 1)] }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assertFindState( editor, @@ -2210,9 +2224,9 @@ suite('FindModel', () => { }); findTest('issue #3516: Control behavior of "Next" operations (not looping back to beginning)', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello', loop: false }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assert.strictEqual(findState.matchesCount, 5); @@ -2290,9 +2304,9 @@ suite('FindModel', () => { }); findTest('issue #3516: Control behavior of "Next" operations (looping back to beginning)', (editor) => { - const findState = new FindReplaceState(); + const findState = disposables.add(new FindReplaceState()); findState.change({ searchString: 'hello' }, false); - const findModel = new FindModelBoundToEditorModel(editor, findState); + const findModel = disposables.add(new FindModelBoundToEditorModel(editor, findState)); assert.strictEqual(findState.matchesCount, 5); diff --git a/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts b/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts index f077b26bc60..cc9e76c93b5 100644 --- a/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/browser/replacePattern.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { parseReplaceString, ReplacePattern, ReplacePiece } from 'vs/editor/contrib/find/browser/replacePattern'; suite('Replace Pattern test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('parse replace string', () => { const testParse = (input: string, expectedPieces: ReplacePiece[]) => { const actual = parseReplaceString(input); diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 2c1e2b2611e..d6353807346 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -5,7 +5,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -31,18 +31,18 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import * as nls from 'vs/nls'; import 'vs/css!./hover'; +import { RunOnceScheduler } from 'vs/base/common/async'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this ; -export class ModesHoverController implements IEditorContribution { +export class ModesHoverController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.hover'; private readonly _toUnhook = new DisposableStore(); - private readonly _didChangeConfigurationHandler: IDisposable; private _contentWidget: ContentHoverController | null; @@ -54,7 +54,10 @@ export class ModesHoverController implements IEditorContribution { private _hoverClicked: boolean; private _isHoverEnabled!: boolean; private _isHoverSticky!: boolean; + private _hidingDelay!: number; private _hoverActivatedByColorDecoratorClick: boolean = false; + private _reactToEditorMouseMoveRunner: RunOnceScheduler; + private _mouseMoveEvent: IEditorMouseEvent | undefined; static get(editor: ICodeEditor): ModesHoverController | null { return editor.getContribution(ModesHoverController.ID); @@ -66,19 +69,24 @@ export class ModesHoverController implements IEditorContribution { @ILanguageService private readonly _languageService: ILanguageService, @IKeybindingService private readonly _keybindingService: IKeybindingService ) { + super(); this._isMouseDown = false; this._hoverClicked = false; this._contentWidget = null; this._glyphWidget = null; + this._reactToEditorMouseMoveRunner = this._register(new RunOnceScheduler(() => this._reactToEditorMouseMove(this._mouseMoveEvent), 0)); this._hookEvents(); - - this._didChangeConfigurationHandler = this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.hover)) { this._unhookEvents(); this._hookEvents(); } - }); + })); + this._register(this._editor.onMouseLeave(() => { + this._mouseMoveEvent = undefined; + this._reactToEditorMouseMoveRunner.cancel(); + })); } private _hookEvents(): void { @@ -87,6 +95,7 @@ export class ModesHoverController implements IEditorContribution { const hoverOpts = this._editor.getOption(EditorOption.hover); this._isHoverEnabled = hoverOpts.enabled; this._isHoverSticky = hoverOpts.sticky; + this._hidingDelay = hoverOpts.hidingDelay; if (this._isHoverEnabled) { this._toUnhook.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._toUnhook.add(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); @@ -152,46 +161,83 @@ export class ModesHoverController implements IEditorContribution { } } - private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { + private _isMouseOverWidget(mouseEvent: IEditorMouseEvent): boolean { const target = mouseEvent.target; - - if (this._contentWidget?.isFocused || this._contentWidget?.isResizing) { - return; - } - - if (this._isMouseDown && this._hoverClicked) { - return; - } - - if (this._isHoverSticky && target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID) { - // mouse moved on top of content hover widget - return; - } - - if (this._isHoverSticky && this._contentWidget?.containsNode(mouseEvent.event.browserEvent.view?.document.activeElement) && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed) { - // selected text within content hover widget - return; - } - if ( - !this._isHoverSticky && target.type === MouseTargetType.CONTENT_WIDGET && target.detail === ContentHoverWidget.ID + this._isHoverSticky + && target.type === MouseTargetType.CONTENT_WIDGET + && target.detail === ContentHoverWidget.ID + ) { + // mouse moved on top of content hover widget + return true; + } + if ( + this._isHoverSticky + && this._contentWidget?.containsNode(mouseEvent.event.browserEvent.view?.document.activeElement) + && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed + ) { + // selected text within content hover widget + return true; + } + if ( + !this._isHoverSticky + && target.type === MouseTargetType.CONTENT_WIDGET + && target.detail === ContentHoverWidget.ID && this._contentWidget?.isColorPickerVisible ) { // though the hover is not sticky, the color picker needs to. - return; + return true; } - - if (this._isHoverSticky && target.type === MouseTargetType.OVERLAY_WIDGET && target.detail === MarginHoverWidget.ID) { + if ( + this._isHoverSticky + && target.type === MouseTargetType.OVERLAY_WIDGET + && target.detail === MarginHoverWidget.ID + ) { // mouse moved on top of overlay hover widget + return true; + } + return false; + } + + private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { + this._mouseMoveEvent = mouseEvent; + if (this._contentWidget?.isFocused || this._contentWidget?.isResizing) { + return; + } + if (this._isMouseDown && this._hoverClicked) { return; } - if (this._isHoverSticky && this._contentWidget?.isVisibleFromKeyboard) { // Sticky mode is on and the hover has been shown via keyboard // so moving the mouse has no effect return; } + const mouseIsOverWidget = this._isMouseOverWidget(mouseEvent); + // If the mouse is over the widget and the hiding timeout is defined, then cancel it + if (mouseIsOverWidget) { + this._reactToEditorMouseMoveRunner.cancel(); + return; + } + + // If the mouse is not over the widget, and if sticky is on, + // then give it a grace period before reacting to the mouse event + if (this._contentWidget?.isVisible && this._isHoverSticky && this._hidingDelay > 0) { + if (!this._reactToEditorMouseMoveRunner.isScheduled()) { + this._reactToEditorMouseMoveRunner.schedule(this._hidingDelay); + } + return; + } + this._reactToEditorMouseMove(mouseEvent); + } + + private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void { + if (!mouseEvent) { + return; + } + + const target = mouseEvent.target; + const mouseOnDecorator = target.element?.classList.contains('colorpicker-color-decoration'); const decoratorActivatedOn = this._editor.getOption(EditorOption.colorDecoratorsActivatedOn); @@ -311,10 +357,10 @@ export class ModesHoverController implements IEditorContribution { return this._contentWidget?.isVisible; } - public dispose(): void { + public override dispose(): void { + super.dispose(); this._unhookEvents(); this._toUnhook.dispose(); - this._didChangeConfigurationHandler.dispose(); this._glyphWidget?.dispose(); this._contentWidget?.dispose(); } diff --git a/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts b/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts index 7aa869ff127..cf05d439031 100644 --- a/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts +++ b/src/vs/editor/contrib/hover/test/browser/contentHover.test.ts @@ -4,13 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover'; -import { Range } from 'vs/editor/common/core/range'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover'; import { IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { TestCodeEditorInstantiationOptions, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('Content Hover', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #151235: Gitlens hover shows up in the wrong place', () => { const text = 'just some text'; withTestCodeEditor(text, {}, (editor) => { diff --git a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts index 054d8a2937c..18ee1b9309c 100644 --- a/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts +++ b/src/vs/editor/contrib/indentation/test/browser/indentation.test.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Selection } from 'vs/editor/common/core/selection'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { NullState } from 'vs/editor/common/languages/nullTokenize'; import { AutoIndentOnPaste, IndentationToSpacesCommand, IndentationToTabsCommand } from 'vs/editor/contrib/indentation/browser/indentation'; -import { testCommand } from 'vs/editor/test/browser/testCommand'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { testCommand } from 'vs/editor/test/browser/testCommand'; import { javascriptIndentationRules } from 'vs/editor/test/common/modes/supports/javascriptIndentationRules'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; -import { MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { NullState } from 'vs/editor/common/languages/nullTokenize'; function testIndentationToSpacesCommand(lines: string[], selection: Selection, tabSize: number, expectedLines: string[], expectedSelection: Selection): void { testCommand(lines, null, selection, (accessor, sel) => new IndentationToSpacesCommand(sel, tabSize), expectedLines, expectedSelection); @@ -29,6 +30,8 @@ function testIndentationToTabsCommand(lines: string[], selection: Selection, tab suite('Editor Contrib - Indentation to Spaces', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('single tabs only at start of line', function () { testIndentationToSpacesCommand( [ @@ -116,6 +119,8 @@ suite('Editor Contrib - Indentation to Spaces', () => { suite('Editor Contrib - Indentation to Tabs', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('spaces only at start of line', function () { testIndentationToTabsCommand( [ @@ -208,6 +213,8 @@ suite('Editor Contrib - Auto Indent On Paste', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #119225: Do not add extra leading space when pasting JSDoc', () => { const languageId = 'leadingSpacePaste'; const model = createTextModel("", languageId, {}); @@ -277,6 +284,8 @@ suite('Editor Contrib - Keep Indent On Paste', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #167299: Blank line removes indent', () => { const languageId = 'blankLineRemovesIndent'; const model = createTextModel("", languageId, {}); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts index 0f7a28ca902..f690a7142d9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts @@ -25,6 +25,7 @@ import { InlineDecorationType } from 'vs/editor/common/viewModel'; import { GhostText, GhostTextReplacement } from 'vs/editor/contrib/inlineCompletions/browser/ghostText'; import { ColumnRange, applyObservableDecorations } from 'vs/editor/contrib/inlineCompletions/browser/utils'; +export const GHOST_TEXT_DESCRIPTION = 'ghost-text'; export interface IGhostTextWidgetModel { readonly targetTextModel: IObservable; readonly ghostText: IObservable; @@ -100,7 +101,7 @@ export class GhostTextWidget extends Disposable { } if (lines.length > 0) { - addToAdditionalLines(lines, 'ghost-text'); + addToAdditionalLines(lines, GHOST_TEXT_DESCRIPTION); if (hiddenTextStartColumn === undefined && part.column <= textBufferLine.length) { hiddenTextStartColumn = part.column; } @@ -151,7 +152,7 @@ export class GhostTextWidget extends Disposable { decorations.push({ range: Range.fromPositions(new Position(uiState.lineNumber, p.column)), options: { - description: 'ghost-text', + description: GHOST_TEXT_DESCRIPTION, after: { content: p.text, inlineClassName: p.preview ? 'ghost-text-decoration-preview' : 'ghost-text-decoration', cursorStops: InjectedTextCursorStops.Left }, showIfCollapsed: true, } diff --git a/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts b/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts index a66ab44d60a..4ef8e7d2cfc 100644 --- a/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts +++ b/src/vs/editor/contrib/lineSelection/test/browser/lineSelection.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; @@ -16,6 +17,9 @@ function executeAction(action: EditorAction, editor: ICodeEditor): void { } suite('LineSelection', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('', () => { const LINE1 = ' \tMy First Line\t '; const LINE2 = '\tMy Second Line'; diff --git a/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts index 16cc2c305f0..c53496fb5de 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/copyLinesCommand.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/browser/copyLinesCommand'; import { DuplicateSelectionAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; @@ -20,6 +21,8 @@ function testCopyLinesUpCommand(lines: string[], selection: Selection, expectedL suite('Editor Contrib - Copy Lines Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('copy first line down', function () { testCopyLinesDownCommand( [ @@ -201,6 +204,8 @@ suite('Editor Contrib - Copy Lines Command', () => { suite('Editor Contrib - Duplicate Selection', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const duplicateSelectionAction = new DuplicateSelectionAction(); function testDuplicateSelectionAction(lines: string[], selections: Selection[], expectedLines: string[], expectedSelections: Selection[]): void { 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 8e7ce3ff8ba..3df2a1f682c 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction } from 'vs/editor/browser/editorExtensions'; @@ -27,6 +28,9 @@ function executeAction(action: EditorAction, editor: ICodeEditor): void { } suite('Editor Contrib - Line Operations', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + suite('SortLinesAscendingAction', () => { test('should sort selected lines in ascending order', function () { withTestCodeEditor( diff --git a/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts index 40544adc2ac..3a05dbc08c8 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/moveLinesCommand.test.ts @@ -2,7 +2,8 @@ * 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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { Selection } from 'vs/editor/common/core/selection'; import { ILanguageService } from 'vs/editor/common/languages/language'; @@ -13,24 +14,46 @@ import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/browser/move import { testCommand } from 'vs/editor/test/browser/testCommand'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void { - testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced, languageConfigurationService), expectedLines, expectedSelection); +function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService?: ILanguageConfigurationService): void { + const disposables = new DisposableStore(); + if (!languageConfigurationService) { + languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); + } + testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Advanced, languageConfigurationService!), expectedLines, expectedSelection); + disposables.dispose(); } -function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void { - testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced, languageConfigurationService), expectedLines, expectedSelection); +function testMoveLinesUpCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService?: ILanguageConfigurationService): void { + const disposables = new DisposableStore(); + if (!languageConfigurationService) { + languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); + } + testCommand(lines, null, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Advanced, languageConfigurationService!), expectedLines, expectedSelection); + disposables.dispose(); } -function testMoveLinesDownWithIndentCommand(languageId: string, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void { - testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full, languageConfigurationService), expectedLines, expectedSelection); +function testMoveLinesDownWithIndentCommand(languageId: string, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService?: ILanguageConfigurationService): void { + const disposables = new DisposableStore(); + if (!languageConfigurationService) { + languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); + } + testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, true, EditorAutoIndentStrategy.Full, languageConfigurationService!), expectedLines, expectedSelection); + disposables.dispose(); } -function testMoveLinesUpWithIndentCommand(languageId: string, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void { - testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full, languageConfigurationService), expectedLines, expectedSelection); +function testMoveLinesUpWithIndentCommand(languageId: string, lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService?: ILanguageConfigurationService): void { + const disposables = new DisposableStore(); + if (!languageConfigurationService) { + languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); + } + testCommand(lines, languageId, selection, (accessor, sel) => new MoveLinesCommand(sel, false, EditorAutoIndentStrategy.Full, languageConfigurationService!), expectedLines, expectedSelection); + disposables.dispose(); } suite('Editor Contrib - Move Lines Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('move first up / last down disabled', function () { testMoveLinesUpCommand( [ @@ -277,6 +300,9 @@ class IndentRulesMode extends Disposable { } suite('Editor contrib - Move Lines Command honors Indentation Rules', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + const indentRules = { decreaseIndentPattern: /^\s*((?!\S.*\/[*]).*[*]\/\s*)?[})\]]|^\s*(case\b.*|default):\s*(\/\/.*|\/[*].*[*]\/\s*)?$/, increaseIndentPattern: /(\{[^}"'`]*|\([^)"']*|\[[^\]"']*|^\s*(\{\}|\(\)|\[\]|(case\b.*|default):))\s*(\/\/.*|\/[*].*[*]\/\s*)?$/, @@ -309,6 +335,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => { mode.dispose(); languageService.dispose(); + languageConfigurationService.dispose(); }); // https://github.com/microsoft/vscode/issues/28552#issuecomment-307867717 @@ -342,6 +369,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => { mode.dispose(); languageService.dispose(); + languageConfigurationService.dispose(); }); @@ -390,6 +418,8 @@ class EnterRulesMode extends Disposable { suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #54829. move block across block', () => { const languageService = new LanguageService(); const languageConfigurationService = new TestLanguageConfigurationService(); @@ -425,5 +455,6 @@ suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => { mode.dispose(); languageService.dispose(); + languageConfigurationService.dispose(); }); }); diff --git a/src/vs/editor/contrib/linesOperations/test/browser/sortLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/browser/sortLinesCommand.test.ts index 630d2bdb2f1..423001a7160 100644 --- a/src/vs/editor/contrib/linesOperations/test/browser/sortLinesCommand.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/browser/sortLinesCommand.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/browser/sortLinesCommand'; import { testCommand } from 'vs/editor/test/browser/testCommand'; @@ -17,6 +18,8 @@ function testSortLinesDescendingCommand(lines: string[], selection: Selection, e suite('Editor Contrib - Sort Lines Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('no op unless at least two lines selected 1', function () { testSortLinesAscendingCommand( [ diff --git a/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts b/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts index 93d7ee89440..982dc07426b 100644 --- a/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts +++ b/src/vs/editor/contrib/linkedEditing/test/browser/linkedEditing.test.ts @@ -6,21 +6,22 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { Handler } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; +import { Handler } from 'vs/editor/common/editorCommon'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { LinkedEditingContribution } from 'vs/editor/contrib/linkedEditing/browser/linkedEditing'; -import { createCodeEditorServices, instantiateTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { instantiateTextModel } from 'vs/editor/test/common/testTextModel'; +import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { DeleteWordLeft } from 'vs/editor/contrib/wordOperations/browser/wordOperations'; import { DeleteAllLeftAction } from 'vs/editor/contrib/linesOperations/browser/linesOperations'; -import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { LinkedEditingContribution } from 'vs/editor/contrib/linkedEditing/browser/linkedEditing'; +import { DeleteWordLeft } from 'vs/editor/contrib/wordOperations/browser/wordOperations'; +import { ITestCodeEditor, createCodeEditorServices, instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { instantiateTextModel } from 'vs/editor/test/common/testTextModel'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; const mockFile = URI.parse('test:somefile.ttt'); const mockFileSelector = { scheme: 'test' }; @@ -57,6 +58,8 @@ suite('linked editing', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + function createMockEditor(text: string | string[]): ITestCodeEditor { const model = disposables.add(instantiateTextModel(instantiationService, typeof text === 'string' ? text : text.join('\n'), languageId, undefined, mockFile)); const editor = disposables.add(instantiateTestCodeEditor(instantiationService, model)); diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index 3786b9daa87..9527453bf32 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -119,6 +119,10 @@ export class LinkDetector extends Disposable implements IEditorContribution { const model = this.editor.getModel(); + if (model.isTooLargeForSyncing()) { + return; + } + if (!this.providers.has(model)) { return; } diff --git a/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts index d206905e4c3..9d36f7ebfd8 100644 --- a/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/browser/multicursor.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Handler } from 'vs/editor/common/editorCommon'; @@ -15,6 +16,8 @@ import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/com suite('Multicursor', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #26393: Multiple cursors + Word wrap', () => { withTestCodeEditor([ 'a'.repeat(20), diff --git a/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts b/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts index 85cda9b2e53..c2d9a57c202 100644 --- a/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts +++ b/src/vs/editor/contrib/semanticTokens/test/browser/documentSemanticTokens.test.ts @@ -4,35 +4,36 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; -import { ModelService } from 'vs/editor/common/services/modelService'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; -import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { Barrier, timeout } from 'vs/base/common/async'; -import { LanguageService } from 'vs/editor/common/services/languageService'; -import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; -import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; -import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { mock } from 'vs/base/test/common/mock'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; -import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ITextModel } from 'vs/editor/common/model'; +import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ModelService } from 'vs/editor/common/services/modelService'; import { SemanticTokensStylingService } from 'vs/editor/common/services/semanticTokensStylingService'; import { DocumentSemanticTokensFeature } from 'vs/editor/contrib/semanticTokens/browser/documentSemanticTokens'; +import { getDocumentSemanticTokens, isSemanticTokens } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/testTextResourcePropertiesService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { mock } from 'vs/base/test/common/mock'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; suite('ModelSemanticColoring', () => { @@ -67,6 +68,8 @@ suite('ModelSemanticColoring', () => { disposables.clear(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('DocumentSemanticTokens should be fetched when the result is empty if there are pending changes', async () => { await runWithFakedTimers({}, async () => { diff --git a/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts b/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts index 8cb75776638..da8dc2f222d 100644 --- a/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts +++ b/src/vs/editor/contrib/semanticTokens/test/browser/getSemanticTokens.test.ts @@ -7,14 +7,17 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextModel } from 'vs/editor/common/model'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; import { DocumentSemanticTokensProvider, ProviderResult, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; import { getDocumentSemanticTokens } from 'vs/editor/contrib/semanticTokens/common/getSemanticTokens'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; suite('getSemanticTokens', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #136540: semantic highlighting flickers', async () => { const disposables = new DisposableStore(); const registry = new LanguageFeatureRegistry(); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts index 1fb079aef00..e0f11350e89 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetController2.old.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; @@ -19,11 +20,20 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace class TestSnippetController extends SnippetController2 { + private _testLanguageConfigurationService: TestLanguageConfigurationService; + constructor( editor: ICodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { - super(editor, new NullLogService(), new LanguageFeaturesService(), _contextKeyService, new TestLanguageConfigurationService()); + const testLanguageConfigurationService = new TestLanguageConfigurationService(); + super(editor, new NullLogService(), new LanguageFeaturesService(), _contextKeyService, testLanguageConfigurationService); + this._testLanguageConfigurationService = testLanguageConfigurationService; + } + + override dispose(): void { + super.dispose(); + this._testLanguageConfigurationService.dispose(); } isInSnippetMode(): boolean { @@ -33,6 +43,8 @@ class TestSnippetController extends SnippetController2 { suite('SnippetController', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function snippetTest(cb: (editor: ITestCodeEditor, template: string, snippetController: TestSnippetController) => void, lines?: string[]): void { if (!lines) { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index b446742a2db..d763a13b280 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -33,7 +33,8 @@ background-color: inherit; } -.monaco-editor .sticky-line-number .codicon { +.monaco-editor .sticky-line-number .codicon-folding-expanded, +.monaco-editor .sticky-line-number .codicon-folding-collapsed { float: right; transition: var(--vscode-editorStickyScroll-foldingOpacityTransition); } @@ -45,7 +46,7 @@ } .monaco-editor .sticky-line-number-inner { - display: block; + display: inline-block; text-align: right; } diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index cc340f143e9..1b2a3a7b305 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -27,6 +27,8 @@ import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/langu import * as dom from 'vs/base/browser/dom'; import { StickyRange } from 'vs/editor/contrib/stickyScroll/browser/stickyScrollElement'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; +import { FoldingModel, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel'; export interface IStickyScrollController { get stickyScrollCandidateProvider(): IStickyLineCandidateProvider; @@ -49,6 +51,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib private readonly _sessionStore: DisposableStore = new DisposableStore(); private _widgetState: StickyScrollWidgetState; + private _foldingModel: FoldingModel | null = null; private _maxStickyLines: number = Number.MAX_SAFE_INTEGER; private _stickyRangeProjectedOnEditor: IRange | undefined; @@ -253,7 +256,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib } if (mouseEvent.shiftKey) { // shift click - const lineIndex = this._stickyScrollWidget.getStickyLineIndexFromChildDomNode(mouseEvent.target); + const lineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(mouseEvent.target); if (lineIndex === null) { return; } @@ -261,6 +264,17 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._revealLineInCenterIfOutsideViewport(position); return; } + const isInFoldingIconDomNode = this._stickyScrollWidget.isInFoldingIconDomNode(mouseEvent.target); + if (isInFoldingIconDomNode) { + // clicked on folding icon + const lineNumber = this._stickyScrollWidget.getLineNumberFromChildDomNode(mouseEvent.target); + this._toggleFoldingRegionForLine(lineNumber); + return; + } + const isInStickyLine = this._stickyScrollWidget.isInStickyLine(mouseEvent.target); + if (!isInStickyLine) { + return; + } // normal click let position = this._stickyScrollWidget.getEditorPositionFromNode(mouseEvent.target); if (!position) { @@ -275,7 +289,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib })); this._register(dom.addStandardDisposableListener(stickyScrollWidgetDomNode, dom.EventType.MOUSE_MOVE, (mouseEvent: IMouseEvent) => { if (mouseEvent.shiftKey) { - const currentEndForLineIndex = this._stickyScrollWidget.getStickyLineIndexFromChildDomNode(mouseEvent.target); + const currentEndForLineIndex = this._stickyScrollWidget.getLineIndexFromChildDomNode(mouseEvent.target); if (currentEndForLineIndex === null || this._showEndForLine !== null && this._showEndForLine === currentEndForLineIndex) { return; } @@ -373,6 +387,25 @@ export class StickyScrollController extends Disposable implements IEditorContrib }); } + private _toggleFoldingRegionForLine(line: number | null) { + if (!this._foldingModel || line === null) { + return; + } + const stickyLine = this._stickyScrollWidget.getStickyLineForLine(line); + const foldingIcon = stickyLine?.foldingIcon; + if (!foldingIcon) { + return; + } + toggleCollapseState(this._foldingModel, Number.MAX_VALUE, [line]); + foldingIcon.isCollapsed = !foldingIcon.isCollapsed; + const scrollTop = (foldingIcon.isCollapsed ? + this._editor.getTopForLineNumber(foldingIcon.foldingEndLine) + : this._editor.getTopForLineNumber(foldingIcon.foldingStartLine)) + - this._editor.getOption(EditorOption.lineHeight) * stickyLine.index + 1; + this._editor.setScrollTop(scrollTop); + this._renderStickyScroll(line); + } + private _readConfiguration() { const options = this._editor.getOption(EditorOption.stickyScroll); if (options.enabled === false) { @@ -421,7 +454,8 @@ export class StickyScrollController extends Disposable implements IEditorContrib private _onTokensChange(event: IModelTokensChangedEvent) { if (this._needsUpdate(event)) { - this._renderStickyScroll(); + // Rebuilding the whole widget from line -1 + this._renderStickyScroll(-1); } } @@ -432,30 +466,32 @@ export class StickyScrollController extends Disposable implements IEditorContrib this._maxStickyLines = Math.round(theoreticalLines * .25); } - private _renderStickyScroll() { + private async _renderStickyScroll(rebuildFromLine: number = Infinity) { const model = this._editor.getModel(); if (!model || model.isTooLargeForTokenization()) { - this._stickyScrollWidget.setState(undefined); + this._foldingModel = null; + this._stickyScrollWidget.setState(undefined, null, rebuildFromLine); return; } const stickyLineVersion = this._stickyLineCandidateProvider.getVersionId(); if (stickyLineVersion === undefined || stickyLineVersion === model.getVersionId()) { + this._foldingModel = await FoldingController.get(this._editor)?.getFoldingModel() ?? null; this._widgetState = this.findScrollWidgetState(); this._stickyScrollVisibleContextKey.set(!(this._widgetState.startLineNumbers.length === 0)); if (!this._focused) { - this._stickyScrollWidget.setState(this._widgetState); + this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); } else { // Suppose that previously the sticky scroll widget had height 0, then if there are visible lines, set the last line as focused if (this._focusedStickyElementIndex === -1) { - this._stickyScrollWidget.setState(this._widgetState); + this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); this._focusedStickyElementIndex = this._stickyScrollWidget.lineNumberCount - 1; if (this._focusedStickyElementIndex !== -1) { this._stickyScrollWidget.focusLineWithIndex(this._focusedStickyElementIndex); } } else { const focusedStickyElementLineNumber = this._stickyScrollWidget.lineNumbers[this._focusedStickyElementIndex]; - this._stickyScrollWidget.setState(this._widgetState); + this._stickyScrollWidget.setState(this._widgetState, this._foldingModel, rebuildFromLine); // Suppose that after setting the state, there are no sticky lines, set the focused index to -1 if (this._stickyScrollWidget.lineNumberCount === 0) { this._focusedStickyElementIndex = -1; diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts index 3269b037cd6..3388380f97c 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollProvider.ts @@ -112,7 +112,7 @@ export class StickyLineCandidateProvider extends Disposable implements IStickyLi private async updateStickyModel(token: CancellationToken): Promise { - if (!this._editor.hasModel() || !this._stickyModelProvider) { + if (!this._editor.hasModel() || !this._stickyModelProvider || this._editor.getModel().isTooLargeForTokenization()) { this._model = null; return; } diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index 763c3c862db..46dd3875f89 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -5,6 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; +import { equals } from 'vs/base/common/arrays'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./stickyScroll'; @@ -16,9 +17,8 @@ import { Position } from 'vs/editor/common/core/position'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { CharacterMapping, RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { foldingCollapsedIcon, foldingExpandedIcon } from 'vs/editor/contrib/folding/browser/foldingDecorations'; -import { FoldingModel, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel'; +import { FoldingModel } from 'vs/editor/contrib/folding/browser/foldingModel'; export class StickyScrollWidgetState { constructor( @@ -27,10 +27,21 @@ export class StickyScrollWidgetState { readonly lastLineRelativePosition: number, readonly showEndForLine: number | null = null ) { } + + equals(other: StickyScrollWidgetState | undefined): boolean { + return !!other + && this.lastLineRelativePosition === other.lastLineRelativePosition + && this.showEndForLine === other.showEndForLine + && equals(this.startLineNumbers, other.startLineNumbers) + && equals(this.endLineNumbers, other.endLineNumbers); + } } const _ttPolicy = createTrustedTypesPolicy('stickyScrollViewLayer', { createHTML: value => value }); -const STICKY_LINE_INDEX_ATTR = 'data-sticky-line-index'; +const STICKY_INDEX_ATTR = 'data-sticky-line-index'; +const STICKY_IS_LINE_ATTR = 'data-sticky-is-line'; +const STICKY_IS_LINE_NUMBER_ATTR = 'data-sticky-is-line-number'; +const STICKY_IS_FOLDING_ICON_ATTR = 'data-sticky-is-folding-icon'; export class StickyScrollWidget extends Disposable implements IOverlayWidget { @@ -40,6 +51,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { private readonly _linesDomNodeScrollable: HTMLElement = document.createElement('div'); private readonly _linesDomNode: HTMLElement = document.createElement('div'); + private _previousState: StickyScrollWidgetState | undefined; private _lineHeight: number = this._editor.getOption(EditorOption.lineHeight); private _stickyLines: RenderedStickyLine[] = []; private _lineNumbers: number[] = []; @@ -106,11 +118,21 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { return this._lineNumbers.length; } + getStickyLineForLine(lineNumber: number): RenderedStickyLine | undefined { + return this._stickyLines.find(stickyLine => stickyLine.lineNumber === lineNumber); + } + getCurrentLines(): readonly number[] { return this._lineNumbers; } - setState(state: StickyScrollWidgetState | undefined): void { + setState(state: StickyScrollWidgetState | undefined, foldingModel: FoldingModel | null, rebuildFromLine: number = Infinity): void { + if (((!this._previousState && !state) || (this._previousState && this._previousState.equals(state))) + && rebuildFromLine === Infinity) { + return; + } + this._previousState = state; + const previousStickyLines = this._stickyLines; this._clearStickyWidget(); if (!state || !this._editor._getViewModel()) { return; @@ -128,7 +150,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { this._lastLineRelativePosition = 0; this._lineNumbers = []; } - this._renderRootNode(); + this._renderRootNode(previousStickyLines, foldingModel, rebuildFromLine); } private _updateWidgetWidth(): void { @@ -161,18 +183,20 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } } - private async _renderRootNode(): Promise { + private async _renderRootNode(previousStickyLines: RenderedStickyLine[], foldingModel: FoldingModel | null, rebuildFromLine: number = Infinity): Promise { - const foldingModel = await FoldingController.get(this._editor)?.getFoldingModel(); const layoutInfo = this._editor.getLayoutInfo(); for (const [index, line] of this._lineNumbers.entries()) { - const renderedStickyLine = this._renderChildNode(index, line, layoutInfo, foldingModel); - if (!renderedStickyLine) { + const previousStickyLine = previousStickyLines[index]; + const stickyLine = (line >= rebuildFromLine || previousStickyLine?.lineNumber !== line) + ? this._renderChildNode(index, line, foldingModel, layoutInfo) + : this._updateTopAndZIndexOfStickyLine(previousStickyLine); + if (!stickyLine) { continue; } - this._linesDomNode.appendChild(renderedStickyLine.lineDomNode); - this._lineNumbersDomNode.appendChild(renderedStickyLine.lineNumberDomNode); - this._stickyLines.push(renderedStickyLine); + this._linesDomNode.appendChild(stickyLine.lineDomNode); + this._lineNumbersDomNode.appendChild(stickyLine.lineNumberDomNode); + this._stickyLines.push(stickyLine); } if (foldingModel) { this._setFoldingHoverListeners(); @@ -211,7 +235,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { })); } - private _renderChildNode(index: number, line: number, layoutInfo: EditorLayoutInfo, foldingModel: FoldingModel | null | undefined): RenderedStickyLine | undefined { + private _renderChildNode(index: number, line: number, foldingModel: FoldingModel | null, layoutInfo: EditorLayoutInfo): RenderedStickyLine | undefined { const viewModel = this._editor._getViewModel(); if (!viewModel) { return; @@ -246,12 +270,18 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } const lineHTMLNode = document.createElement('span'); + lineHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index)); + lineHTMLNode.setAttribute(STICKY_IS_LINE_ATTR, ''); + lineHTMLNode.setAttribute('role', 'listitem'); + lineHTMLNode.tabIndex = 0; lineHTMLNode.className = 'sticky-line-content'; lineHTMLNode.classList.add(`stickyLine${line}`); lineHTMLNode.style.lineHeight = `${this._lineHeight}px`; lineHTMLNode.innerHTML = newLine as string; const lineNumberHTMLNode = document.createElement('span'); + lineNumberHTMLNode.setAttribute(STICKY_INDEX_ATTR, String(index)); + lineNumberHTMLNode.setAttribute(STICKY_IS_LINE_NUMBER_ATTR, ''); lineNumberHTMLNode.className = 'sticky-line-number'; lineNumberHTMLNode.style.lineHeight = `${this._lineHeight}px`; const lineNumbersWidth = layoutInfo.contentLeft; @@ -269,21 +299,28 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { innerLineNumberHTML.style.paddingLeft = `${layoutInfo.lineNumbersLeft}px`; lineNumberHTMLNode.appendChild(innerLineNumberHTML); - const foldingIcon = this._renderFoldingIconForLine(lineNumberHTMLNode, foldingModel, index, line); + const foldingIcon = this._renderFoldingIconForLine(foldingModel, line); + if (foldingIcon) { + lineNumberHTMLNode.appendChild(foldingIcon.domNode); + } this._editor.applyFontInfo(lineHTMLNode); this._editor.applyFontInfo(innerLineNumberHTML); - lineHTMLNode.setAttribute('role', 'listitem'); - lineHTMLNode.setAttribute(STICKY_LINE_INDEX_ATTR, String(index)); - lineHTMLNode.tabIndex = 0; lineNumberHTMLNode.style.lineHeight = `${this._lineHeight}px`; lineHTMLNode.style.lineHeight = `${this._lineHeight}px`; lineNumberHTMLNode.style.height = `${this._lineHeight}px`; lineHTMLNode.style.height = `${this._lineHeight}px`; - // Special case for the last line of sticky scroll + const renderedLine = new RenderedStickyLine(index, line, lineHTMLNode, lineNumberHTMLNode, foldingIcon, renderOutput.characterMapping); + return this._updateTopAndZIndexOfStickyLine(renderedLine); + } + + private _updateTopAndZIndexOfStickyLine(stickyLine: RenderedStickyLine): RenderedStickyLine { + const index = stickyLine.index; + const lineHTMLNode = stickyLine.lineDomNode; + const lineNumberHTMLNode = stickyLine.lineNumberDomNode; const isLastLine = index === this._lineNumbers.length - 1; const lastLineZIndex = '0'; @@ -291,14 +328,14 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { lineHTMLNode.style.zIndex = isLastLine ? lastLineZIndex : intermediateLineZIndex; lineNumberHTMLNode.style.zIndex = isLastLine ? lastLineZIndex : intermediateLineZIndex; - const lastLineTop = `${index * this._lineHeight + this._lastLineRelativePosition + (foldingIcon?.isCollapsed ? 1 : 0)}px`; + const lastLineTop = `${index * this._lineHeight + this._lastLineRelativePosition + (stickyLine.foldingIcon?.isCollapsed ? 1 : 0)}px`; const intermediateLineTop = `${index * this._lineHeight}px`; lineHTMLNode.style.top = isLastLine ? lastLineTop : intermediateLineTop; lineNumberHTMLNode.style.top = isLastLine ? lastLineTop : intermediateLineTop; - return new RenderedStickyLine(line, lineHTMLNode, lineNumberHTMLNode, foldingIcon, renderOutput.characterMapping); + return stickyLine; } - private _renderFoldingIconForLine(container: HTMLSpanElement, foldingModel: FoldingModel | null | undefined, index: number, line: number): StickyFoldingIcon | undefined { + private _renderFoldingIconForLine(foldingModel: FoldingModel | null, line: number): StickyFoldingIcon | undefined { const showFoldingControls: 'mouseover' | 'always' | 'never' = this._editor.getOption(EditorOption.showFoldingControls); if (!foldingModel || showFoldingControls === 'never') { return; @@ -311,20 +348,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { return; } const isCollapsed = foldingRegions.isCollapsed(indexOfFoldingRegion); - const foldingIcon = new StickyFoldingIcon(isCollapsed, this._lineHeight); - container.append(foldingIcon.domNode); + const foldingIcon = new StickyFoldingIcon(isCollapsed, startLineNumber, foldingRegions.getEndLineNumber(indexOfFoldingRegion), this._lineHeight); foldingIcon.setVisible(this._isOnGlyphMargin ? true : (isCollapsed || showFoldingControls === 'always')); - - this._foldingIconStore.add(dom.addDisposableListener(foldingIcon.domNode, dom.EventType.CLICK, () => { - toggleCollapseState(foldingModel, Number.MAX_VALUE, [line]); - foldingIcon.isCollapsed = !isCollapsed; - const scrollTop = - (isCollapsed ? - this._editor.getTopForLineNumber(startLineNumber) - : this._editor.getTopForLineNumber(foldingRegions.getEndLineNumber(indexOfFoldingRegion))) - - this._lineHeight * index + 1; - this._editor.setScrollTop(scrollTop); - })); + foldingIcon.domNode.setAttribute(STICKY_IS_FOLDING_ICON_ATTR, ''); return foldingIcon; } @@ -383,7 +409,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } private _getRenderedStickyLineFromChildDomNode(domNode: HTMLElement | null): RenderedStickyLine | null { - const index = this.getStickyLineIndexFromChildDomNode(domNode); + const index = this.getLineIndexFromChildDomNode(domNode); if (index === null || index < 0 || index >= this._stickyLines.length) { return null; } @@ -391,23 +417,51 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { } /** - * Given a child dom node, tries to find the line number attribute - * that was stored in the node. Returns null if none is found. + * Given a child dom node, tries to find the line number attribute that was stored in the node. + * @returns the attribute value or null if none is found. */ - getStickyLineIndexFromChildDomNode(domNode: HTMLElement | null): number | null { + getLineIndexFromChildDomNode(domNode: HTMLElement | null): number | null { + const lineIndex = this._getAttributeValue(domNode, STICKY_INDEX_ATTR); + return lineIndex ? parseInt(lineIndex, 10) : null; + } + + /** + * Given a child dom node, tries to find if it is (contained in) a sticky line. + * @returns a boolean. + */ + isInStickyLine(domNode: HTMLElement | null): boolean { + const isInLine = this._getAttributeValue(domNode, STICKY_IS_LINE_ATTR); + return isInLine !== undefined; + } + + /** + * Given a child dom node, tries to find if this dom node is (contained in) a sticky folding icon. + * @returns a boolean. + */ + isInFoldingIconDomNode(domNode: HTMLElement | null): boolean { + const isInFoldingIcon = this._getAttributeValue(domNode, STICKY_IS_FOLDING_ICON_ATTR); + return isInFoldingIcon !== undefined; + } + + /** + * Given the dom node, finds if it or its parent sequence contains the given attribute. + * @returns the attribute value or undefined. + */ + private _getAttributeValue(domNode: HTMLElement | null, attribute: string): string | undefined { while (domNode && domNode !== this._rootDomNode) { - const line = domNode.getAttribute(STICKY_LINE_INDEX_ATTR); - if (line) { - return parseInt(line, 10); + const line = domNode.getAttribute(attribute); + if (line !== null) { + return line; } domNode = domNode.parentElement; } - return null; + return; } } class RenderedStickyLine { constructor( + public readonly index: number, public readonly lineNumber: number, public readonly lineDomNode: HTMLElement, public readonly lineNumberDomNode: HTMLElement, @@ -422,6 +476,8 @@ class StickyFoldingIcon { constructor( public isCollapsed: boolean, + public foldingStartLine: number, + public foldingEndLine: number, public dimension: number ) { this.domNode = document.createElement('div'); diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index a11868bc8cb..943bad34e80 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -490,7 +490,7 @@ export class WordHighlighterContribution extends Disposable implements IEditorCo this.wordHighlighter = null; this.linkedContributions = new Set(); const createWordHighlighterIfPossible = () => { - if (editor.hasModel()) { + if (editor.hasModel() && !editor.getModel().isTooLargeForTokenization()) { this.wordHighlighter = new WordHighlighter(editor, languageFeaturesService.documentHighlightProvider, () => Iterable.map(this.linkedContributions, c => c.wordHighlighter), contextKeyService); } }; diff --git a/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts b/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts index 2eaf5d3097c..399d020170e 100644 --- a/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts +++ b/src/vs/editor/contrib/wordOperations/test/browser/wordOperations.test.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; -import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/browser/wordTestUtils'; import { CursorWordAccessibilityLeft, CursorWordAccessibilityLeftSelect, CursorWordAccessibilityRight, CursorWordAccessibilityRightSelect, CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorWordEndRightSelect, CursorWordLeft, CursorWordLeftSelect, CursorWordRight, CursorWordRightSelect, CursorWordStartLeft, CursorWordStartLeftSelect, CursorWordStartRight, CursorWordStartRightSelect, DeleteInsideWord, DeleteWordEndLeft, DeleteWordEndRight, DeleteWordLeft, DeleteWordRight, DeleteWordStartLeft, DeleteWordStartRight } from 'vs/editor/contrib/wordOperations/browser/wordOperations'; +import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/browser/wordTestUtils'; import { createCodeEditorServices, instantiateTestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ILanguageService } from 'vs/editor/common/languages/language'; suite('WordOperations', () => { @@ -61,6 +62,8 @@ suite('WordOperations', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void { instantiationService.invokeFunction((accessor) => { command.runEditorCommand(accessor, editor, null); diff --git a/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts index fe773be9c4b..12258f71cfb 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/browser/wordPartOperations.test.ts @@ -4,16 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { deserializePipePositions, serializePipePositions, testRepeatedActionAndExtractPositions } from 'vs/editor/contrib/wordOperations/test/browser/wordTestUtils'; -import { StaticServiceAccessor } from 'vs/editor/contrib/wordPartOperations/test/browser/utils'; import { CursorWordPartLeft, CursorWordPartLeftSelect, CursorWordPartRight, CursorWordPartRightSelect, DeleteWordPartLeft, DeleteWordPartRight } from 'vs/editor/contrib/wordPartOperations/browser/wordPartOperations'; +import { StaticServiceAccessor } from 'vs/editor/contrib/wordPartOperations/test/browser/utils'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; suite('WordPartOperations', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + const _deleteWordPartLeft = new DeleteWordPartLeft(); const _deleteWordPartRight = new DeleteWordPartRight(); const _cursorWordPartLeft = new CursorWordPartLeft(); diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts index f5b83651667..fff134d4902 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts @@ -27,17 +27,17 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService { @IThemeService themeService: IThemeService, ) { super(themeService); - this.onCodeEditorAdd(() => this._checkContextKey()); - this.onCodeEditorRemove(() => this._checkContextKey()); + this._register(this.onCodeEditorAdd(() => this._checkContextKey())); + this._register(this.onCodeEditorRemove(() => this._checkContextKey())); this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false); this._activeCodeEditor = null; - this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => { + this._register(this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => { if (!source) { return null; } return this.doOpenEditor(source, input); - }); + })); } private _checkContextKey(): void { diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index a391249e6e0..cac2e96a01f 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -244,7 +244,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME)); this._knownThemes.set(HC_LIGHT_THEME_NAME, newBuiltInTheme(HC_LIGHT_THEME_NAME)); - const iconsStyleSheet = getIconsStyleSheet(this); + const iconsStyleSheet = this._register(getIconsStyleSheet(this)); this._codiconCSS = iconsStyleSheet.getCSS(); this._themeCSS = ''; @@ -255,10 +255,10 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe this.setTheme(VS_LIGHT_THEME_NAME); this._onOSSchemeChanged(); - iconsStyleSheet.onDidChange(() => { + this._register(iconsStyleSheet.onDidChange(() => { this._codiconCSS = iconsStyleSheet.getCSS(); this._updateCSS(); - }); + })); addMatchMediaChangeListener('(forced-colors: active)', () => { this._onOSSchemeChanged(); diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index 922e6c6e251..d0115c98dc1 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -8,7 +8,7 @@ * using regular expressions. */ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as languages from 'vs/editor/common/languages'; import { NullState, nullTokenizeEncoded, nullTokenize } from 'vs/editor/common/languages/nullTokenize'; import { TokenTheme } from 'vs/editor/common/languages/supports/tokenization'; @@ -387,7 +387,7 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { export type ILoadStatus = { loaded: true } | { loaded: false; promise: Promise }; -export class MonarchTokenizer implements languages.ITokenizationSupport, IDisposable { +export class MonarchTokenizer extends Disposable implements languages.ITokenizationSupport, IDisposable { private readonly _languageService: ILanguageService; private readonly _standaloneThemeService: IStandaloneThemeService; @@ -395,10 +395,10 @@ export class MonarchTokenizer implements languages.ITokenizationSupport, IDispos private readonly _lexer: monarchCommon.ILexer; private readonly _embeddedLanguages: { [languageId: string]: boolean }; public embeddedLoaded: Promise; - private readonly _tokenizationRegistryListener: IDisposable; private _maxTokenizationLineLength: number; constructor(languageService: ILanguageService, standaloneThemeService: IStandaloneThemeService, languageId: string, lexer: monarchCommon.ILexer, @IConfigurationService private readonly _configurationService: IConfigurationService) { + super(); this._languageService = languageService; this._standaloneThemeService = standaloneThemeService; this._languageId = languageId; @@ -408,7 +408,7 @@ export class MonarchTokenizer implements languages.ITokenizationSupport, IDispos // Set up listening for embedded modes let emitting = false; - this._tokenizationRegistryListener = languages.TokenizationRegistry.onDidChange((e) => { + this._register(languages.TokenizationRegistry.onDidChange((e) => { if (emitting) { return; } @@ -425,21 +425,17 @@ export class MonarchTokenizer implements languages.ITokenizationSupport, IDispos languages.TokenizationRegistry.handleChange([this._languageId]); emitting = false; } - }); + })); this._maxTokenizationLineLength = this._configurationService.getValue('editor.maxTokenizationLineLength', { overrideIdentifier: this._languageId }); - this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor.maxTokenizationLineLength')) { this._maxTokenizationLineLength = this._configurationService.getValue('editor.maxTokenizationLineLength', { overrideIdentifier: this._languageId }); } - }); - } - - public dispose(): void { - this._tokenizationRegistryListener.dispose(); + })); } public getLoadStatus(): ILoadStatus { diff --git a/src/vs/editor/standalone/test/browser/monarch.test.ts b/src/vs/editor/standalone/test/browser/monarch.test.ts index da034e57b4e..881ea18973b 100644 --- a/src/vs/editor/standalone/test/browser/monarch.test.ts +++ b/src/vs/editor/standalone/test/browser/monarch.test.ts @@ -4,18 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LanguageService } from 'vs/editor/common/services/languageService'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; -import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile'; -import { Token, TokenizationRegistry } from 'vs/editor/common/languages'; -import { IMonarchLanguage } from 'vs/editor/standalone/common/monarch/monarchTypes'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Token, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { LanguageService } from 'vs/editor/common/services/languageService'; import { StandaloneConfigurationService } from 'vs/editor/standalone/browser/standaloneServices'; +import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile'; +import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; +import { IMonarchLanguage } from 'vs/editor/standalone/common/monarch/monarchTypes'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; suite('Monarch', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function createMonarchTokenizer(languageService: ILanguageService, languageId: string, language: IMonarchLanguage, configurationService: IConfigurationService): MonarchTokenizer { return new MonarchTokenizer(languageService, null!, languageId, compile(languageId, language), configurationService); } @@ -36,15 +39,15 @@ suite('Monarch', () => { const languageService = disposables.add(new LanguageService()); const configurationService = new StandaloneConfigurationService(); disposables.add(languageService.registerLanguage({ id: 'sql' })); - disposables.add(TokenizationRegistry.register('sql', createMonarchTokenizer(languageService, 'sql', { + disposables.add(TokenizationRegistry.register('sql', disposables.add(createMonarchTokenizer(languageService, 'sql', { tokenizer: { root: [ [/./, 'token'] ] } - }, configurationService))); + }, configurationService)))); const SQL_QUERY_START = '(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)'; - const tokenizer = createMonarchTokenizer(languageService, 'test1', { + const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test1', { tokenizer: { root: [ [`(\"\"\")${SQL_QUERY_START}`, [{ 'token': 'string.quote', }, { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },]], @@ -66,7 +69,7 @@ suite('Monarch', () => { ], endStringWithSQL: [[/"""/, { token: 'string.quote', next: '@popall', nextEmbedded: '@pop', },]], } - }, configurationService); + }, configurationService)); const lines = [ `mysql_query("""SELECT * FROM table_name WHERE ds = ''""")`, @@ -109,9 +112,10 @@ suite('Monarch', () => { }); test('microsoft/monaco-editor#1235: Empty Line Handling', () => { + const disposables = new DisposableStore(); const configurationService = new StandaloneConfigurationService(); - const languageService = new LanguageService(); - const tokenizer = createMonarchTokenizer(languageService, 'test', { + const languageService = disposables.add(new LanguageService()); + const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test', { tokenizer: { root: [ { include: '@comments' }, @@ -129,7 +133,7 @@ suite('Monarch', () => { // No possible rule to detect an empty line and @pop? ], }, - }, configurationService); + }, configurationService)); const lines = [ `// This comment \\`, @@ -162,14 +166,15 @@ suite('Monarch', () => { [], [new Token(0, 'source.test', 'test')] ]); - languageService.dispose(); + disposables.dispose(); }); test('microsoft/monaco-editor#2265: Exit a state at end of line', () => { + const disposables = new DisposableStore(); const configurationService = new StandaloneConfigurationService(); - const languageService = new LanguageService(); - const tokenizer = createMonarchTokenizer(languageService, 'test', { + const languageService = disposables.add(new LanguageService()); + const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test', { includeLF: true, tokenizer: { root: [ @@ -184,7 +189,7 @@ suite('Monarch', () => { [/[^\d]+/, ''] ] } - }, configurationService); + }, configurationService)); const lines = [ `PRINT 10 * 20`, @@ -212,14 +217,16 @@ suite('Monarch', () => { new Token(18, 'number.test', 'test'), ] ]); - languageService.dispose(); + + disposables.dispose(); }); test('issue #115662: monarchCompile function need an extra option which can control replacement', () => { + const disposables = new DisposableStore(); const configurationService = new StandaloneConfigurationService(); - const languageService = new LanguageService(); + const languageService = disposables.add(new LanguageService()); - const tokenizer1 = createMonarchTokenizer(languageService, 'test', { + const tokenizer1 = disposables.add(createMonarchTokenizer(languageService, 'test', { ignoreCase: false, uselessReplaceKey1: '@uselessReplaceKey2', uselessReplaceKey2: '@uselessReplaceKey3', @@ -236,9 +243,9 @@ suite('Monarch', () => { }, ], }, - }, configurationService); + }, configurationService)); - const tokenizer2 = createMonarchTokenizer(languageService, 'test', { + const tokenizer2 = disposables.add(createMonarchTokenizer(languageService, 'test', { ignoreCase: false, tokenizer: { root: [ @@ -248,7 +255,7 @@ suite('Monarch', () => { }, ], }, - }, configurationService); + }, configurationService)); const lines = [ `@ham` @@ -267,14 +274,16 @@ suite('Monarch', () => { new Token(0, 'ham.test', 'test'), ] ]); - languageService.dispose(); + + disposables.dispose(); }); test('microsoft/monaco-editor#2424: Allow to target @@', () => { + const disposables = new DisposableStore(); const configurationService = new StandaloneConfigurationService(); - const languageService = new LanguageService(); + const languageService = disposables.add(new LanguageService()); - const tokenizer = createMonarchTokenizer(languageService, 'test', { + const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test', { ignoreCase: false, tokenizer: { root: [ @@ -284,7 +293,7 @@ suite('Monarch', () => { }, ], }, - }, configurationService); + }, configurationService)); const lines = [ `@@` @@ -296,17 +305,20 @@ suite('Monarch', () => { new Token(0, 'ham.test', 'test'), ] ]); - languageService.dispose(); + + disposables.dispose(); }); test('microsoft/monaco-editor#3025: Check maxTokenizationLineLength before tokenizing', async () => { + const disposables = new DisposableStore(); + const configurationService = new StandaloneConfigurationService(); - const languageService = new LanguageService(); + const languageService = disposables.add(new LanguageService()); // Set maxTokenizationLineLength to 4 so that "ham" works but "hamham" would fail await configurationService.updateValue('editor.maxTokenizationLineLength', 4); - const tokenizer = createMonarchTokenizer(languageService, 'test', { + const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test', { tokenizer: { root: [ { @@ -315,7 +327,7 @@ suite('Monarch', () => { }, ], }, - }, configurationService); + }, configurationService)); const lines = [ 'ham', // length 3, should be tokenized @@ -330,7 +342,8 @@ suite('Monarch', () => { new Token(0, '', 'test') ] ]); - languageService.dispose(); + + disposables.dispose(); }); }); diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index b4a255004f6..92e385564ac 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -7,8 +7,9 @@ import * as assert from 'assert'; import { Color } from 'vs/base/common/color'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Token, IState } from 'vs/editor/common/languages'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguageId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { IState, Token } from 'vs/editor/common/languages'; import { TokenTheme } from 'vs/editor/common/languages/supports/tokenization'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { ILineTokens, IToken, TokenizationSupportAdapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; @@ -16,10 +17,12 @@ import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from import { UnthemedProductIconTheme } from 'vs/platform/theme/browser/iconsStyleSheet'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { IFileIconTheme, IColorTheme, ITokenStyle, IProductIconTheme } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IFileIconTheme, IProductIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; suite('TokenizationSupport2Adapter', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const languageId = 'tttt'; // const tokenMetadata = (LanguageId.PlainText << MetadataConsts.LANGUAGEID_OFFSET); diff --git a/src/vs/editor/standalone/test/browser/standaloneServices.test.ts b/src/vs/editor/standalone/test/browser/standaloneServices.test.ts index ce37510a9bf..e716ea922d5 100644 --- a/src/vs/editor/standalone/test/browser/standaloneServices.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneServices.test.ts @@ -5,8 +5,10 @@ import * as assert from 'assert'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { StandaloneConfigurationService, StandaloneNotificationService, StandaloneCommandService, StandaloneKeybindingService } from 'vs/editor/standalone/browser/standaloneServices'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandaloneCodeEditorService } from 'vs/editor/standalone/browser/standaloneCodeEditorService'; +import { StandaloneCommandService, StandaloneConfigurationService, StandaloneKeybindingService, StandaloneNotificationService } from 'vs/editor/standalone/browser/standaloneServices'; import { StandaloneThemeService } from 'vs/editor/standalone/browser/standaloneThemeService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -17,6 +19,8 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil suite('StandaloneKeybindingService', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + class TestStandaloneKeybindingService extends StandaloneKeybindingService { public testDispatch(e: IKeyboardEvent): void { super._dispatch(e, null!); @@ -25,20 +29,21 @@ suite('StandaloneKeybindingService', () => { test('issue microsoft/monaco-editor#167', () => { + const disposables = new DisposableStore(); const serviceCollection = new ServiceCollection(); const instantiationService = new InstantiationService(serviceCollection, true); const configurationService = new StandaloneConfigurationService(); - const contextKeyService = new ContextKeyService(configurationService); + const contextKeyService = disposables.add(new ContextKeyService(configurationService)); const commandService = new StandaloneCommandService(instantiationService); const notificationService = new StandaloneNotificationService(); - const standaloneThemeService = new StandaloneThemeService(); - const codeEditorService = new StandaloneCodeEditorService(contextKeyService, standaloneThemeService); - const keybindingService = new TestStandaloneKeybindingService(contextKeyService, commandService, NullTelemetryService, notificationService, new NullLogService(), codeEditorService); + const standaloneThemeService = disposables.add(new StandaloneThemeService()); + const codeEditorService = disposables.add(new StandaloneCodeEditorService(contextKeyService, standaloneThemeService)); + const keybindingService = disposables.add(new TestStandaloneKeybindingService(contextKeyService, commandService, NullTelemetryService, notificationService, new NullLogService(), codeEditorService)); let commandInvoked = false; - keybindingService.addDynamicKeybinding('testCommand', KeyCode.F9, () => { + disposables.add(keybindingService.addDynamicKeybinding('testCommand', KeyCode.F9, () => { commandInvoked = true; - }, undefined); + }, undefined)); keybindingService.testDispatch({ _standardKeyboardEventBrand: true, @@ -52,5 +57,7 @@ suite('StandaloneKeybindingService', () => { }); assert.ok(commandInvoked, 'command invoked'); + + disposables.dispose(); }); }); diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 238bc948d6c..0de5fe05983 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { getEditOperation, testCommand } from 'vs/editor/test/browser/testCommand'; -import { withEditorModel } from 'vs/editor/test/common/testTextModel'; -import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; -import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { getEditOperation, testCommand } from 'vs/editor/test/browser/testCommand'; +import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { withEditorModel } from 'vs/editor/test/common/testTextModel'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; /** @@ -82,6 +83,8 @@ function prepareDocBlockCommentLanguage(accessor: ServicesAccessor, disposables: suite('Editor Commands - ShiftCommand', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + // --------- shift test('Bug 9503: Shifting without any selection', () => { @@ -683,7 +686,7 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: true, useTabStops: false, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()), + }, accessor.get(ILanguageConfigurationService)), [ ' Written | Numeric', ' one | 1', @@ -729,7 +732,7 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: true, useTabStops: false, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()), + }, accessor.get(ILanguageConfigurationService)), [ ' Written | Numeric', ' one | 1', @@ -775,7 +778,7 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: false, useTabStops: false, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()), + }, accessor.get(ILanguageConfigurationService)), [ ' Written | Numeric', ' one | 1', @@ -821,7 +824,7 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: true, useTabStops: false, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()), + }, accessor.get(ILanguageConfigurationService)), [ ' Written | Numeric', ' one | 1', @@ -856,7 +859,7 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: false, useTabStops: true, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()), + }, accessor.get(ILanguageConfigurationService)), [ '\tHello world!', 'another line' @@ -960,6 +963,7 @@ suite('Editor Commands - ShiftCommand', () => { function _assertUnshiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: ISingleEditOperation[]): void { return withEditorModel(text, (model) => { + const testLanguageConfigurationService = new TestLanguageConfigurationService(); const op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: true, tabSize: tabSize, @@ -967,14 +971,16 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: insertSpaces, useTabStops: true, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()); + }, testLanguageConfigurationService); const actual = getEditOperation(model, op); assert.deepStrictEqual(actual, expected); + testLanguageConfigurationService.dispose(); }); } function _assertShiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: ISingleEditOperation[]): void { return withEditorModel(text, (model) => { + const testLanguageConfigurationService = new TestLanguageConfigurationService(); const op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: false, tabSize: tabSize, @@ -982,9 +988,10 @@ suite('Editor Commands - ShiftCommand', () => { insertSpaces: insertSpaces, useTabStops: true, autoIndent: EditorAutoIndentStrategy.Full, - }, new TestLanguageConfigurationService()); + }, testLanguageConfigurationService); const actual = getEditOperation(model, op); assert.deepStrictEqual(actual, expected); + testLanguageConfigurationService.dispose(); }); } }); diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index 391fad09dcf..6cdb2657aa7 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation, ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -28,6 +29,8 @@ function testCommand(lines: string[], selections: Selection[], edits: ISingleEdi suite('Editor Side Editing - collapsed selection', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('replace at selection', () => { testCommand( [ @@ -186,6 +189,8 @@ suite('Editor Side Editing - collapsed selection', () => { suite('SideEditing', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const LINES = [ 'My First Line', 'My Second Line', diff --git a/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts b/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts index 0c87ceb9be1..d440e01d723 100644 --- a/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts +++ b/src/vs/editor/test/browser/commands/trimTrailingWhitespaceCommand.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TrimTrailingWhitespaceCommand, trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; @@ -50,6 +51,8 @@ function assertTrimTrailingWhitespace(text: string[], cursors: Position[], expec suite('Editor Commands - Trim Trailing Whitespace Command', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('remove trailing whitespace', function () { assertTrimTrailingWhitespaceCommand([''], []); assertTrimTrailingWhitespaceCommand(['text'], []); diff --git a/src/vs/editor/test/browser/config/editorConfiguration.test.ts b/src/vs/editor/test/browser/config/editorConfiguration.test.ts index b07b4c79487..b507c54686f 100644 --- a/src/vs/editor/test/browser/config/editorConfiguration.test.ts +++ b/src/vs/editor/test/browser/config/editorConfiguration.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvConfiguration } from 'vs/editor/browser/config/editorConfiguration'; import { migrateOptions } from 'vs/editor/browser/config/migrateOptions'; import { ConfigurationChangedEvent, EditorOption, IEditorHoverOptions, IQuickSuggestionsOptions } from 'vs/editor/common/config/editorOptions'; @@ -12,6 +13,9 @@ import { TestConfiguration } from 'vs/editor/test/browser/config/testConfigurati import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; suite('Common Editor Config', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Zoom Level', () => { //Zoom levels are defined to go between -5, 20 inclusive @@ -77,6 +81,7 @@ suite('Common Editor Config', () => { test('wordWrap default', () => { const config = new TestWrappingConfiguration({}); assertWrapping(config, false, -1); + config.dispose(); }); test('wordWrap compat false', () => { @@ -84,6 +89,7 @@ suite('Common Editor Config', () => { wordWrap: false }); assertWrapping(config, false, -1); + config.dispose(); }); test('wordWrap compat true', () => { @@ -91,6 +97,7 @@ suite('Common Editor Config', () => { wordWrap: true }); assertWrapping(config, true, 80); + config.dispose(); }); test('wordWrap on', () => { @@ -98,6 +105,7 @@ suite('Common Editor Config', () => { wordWrap: 'on' }); assertWrapping(config, true, 80); + config.dispose(); }); test('wordWrap on without minimap', () => { @@ -108,6 +116,7 @@ suite('Common Editor Config', () => { } }); assertWrapping(config, true, 88); + config.dispose(); }); test('wordWrap on does not use wordWrapColumn', () => { @@ -116,6 +125,7 @@ suite('Common Editor Config', () => { wordWrapColumn: 10 }); assertWrapping(config, true, 80); + config.dispose(); }); test('wordWrap off', () => { @@ -123,6 +133,7 @@ suite('Common Editor Config', () => { wordWrap: 'off' }); assertWrapping(config, false, -1); + config.dispose(); }); test('wordWrap off does not use wordWrapColumn', () => { @@ -131,6 +142,7 @@ suite('Common Editor Config', () => { wordWrapColumn: 10 }); assertWrapping(config, false, -1); + config.dispose(); }); test('wordWrap wordWrapColumn uses default wordWrapColumn', () => { @@ -138,6 +150,7 @@ suite('Common Editor Config', () => { wordWrap: 'wordWrapColumn' }); assertWrapping(config, false, 80); + config.dispose(); }); test('wordWrap wordWrapColumn uses wordWrapColumn', () => { @@ -146,6 +159,7 @@ suite('Common Editor Config', () => { wordWrapColumn: 100 }); assertWrapping(config, false, 100); + config.dispose(); }); test('wordWrap wordWrapColumn validates wordWrapColumn', () => { @@ -154,6 +168,7 @@ suite('Common Editor Config', () => { wordWrapColumn: -1 }); assertWrapping(config, false, 1); + config.dispose(); }); test('wordWrap bounded uses default wordWrapColumn', () => { @@ -161,6 +176,7 @@ suite('Common Editor Config', () => { wordWrap: 'bounded' }); assertWrapping(config, true, 80); + config.dispose(); }); test('wordWrap bounded uses wordWrapColumn', () => { @@ -169,6 +185,7 @@ suite('Common Editor Config', () => { wordWrapColumn: 40 }); assertWrapping(config, true, 40); + config.dispose(); }); test('wordWrap bounded validates wordWrapColumn', () => { @@ -177,6 +194,7 @@ suite('Common Editor Config', () => { wordWrapColumn: -1 }); assertWrapping(config, true, 1); + config.dispose(); }); test('issue #53152: Cannot assign to read only property \'enabled\' of object', () => { @@ -190,17 +208,21 @@ suite('Common Editor Config', () => { assert.strictEqual(config.options.get(EditorOption.hover).enabled, true); config.updateOptions({ hover: { enabled: false } }); assert.strictEqual(config.options.get(EditorOption.hover).enabled, false); + + config.dispose(); }); test('does not emit event when nothing changes', () => { const config = new TestConfiguration({ glyphMargin: true, roundedSelection: false }); let event: ConfigurationChangedEvent | null = null; - config.onDidChange(e => event = e); + const disposable = config.onDidChange(e => event = e); assert.strictEqual(config.options.get(EditorOption.glyphMargin), true); config.updateOptions({ glyphMargin: true }); config.updateOptions({ roundedSelection: false }); assert.strictEqual(event, null); + config.dispose(); + disposable.dispose(); }); test('issue #94931: Unable to open source file', () => { @@ -211,6 +233,7 @@ suite('Common Editor Config', () => { comments: 'off', strings: 'off' }); + config.dispose(); }); test('issue #102920: Can\'t snap or split view with JSON files', () => { @@ -222,6 +245,7 @@ suite('Common Editor Config', () => { comments: 'off', strings: 'on' }); + config.dispose(); }); test('issue #151926: Untyped editor options apply', () => { @@ -239,10 +263,14 @@ suite('Common Editor Config', () => { allowedLocales: { "_os": true, "_vscode": true } } ); + config.dispose(); }); }); suite('migrateOptions', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function migrate(options: any): any { migrateOptions(options); return options; diff --git a/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts b/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts index 87882eeb24b..ff4a6acb7c4 100644 --- a/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorLayoutInfo, EditorLayoutInfoComputer, RenderMinimap, EditorOption, EditorMinimapOptions, InternalEditorScrollbarOptions, EditorOptions, RenderLineNumbersType, InternalEditorRenderLineNumbersOptions } from 'vs/editor/common/config/editorOptions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ComputedEditorOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { EditorLayoutInfo, EditorLayoutInfoComputer, EditorMinimapOptions, EditorOption, EditorOptions, InternalEditorRenderLineNumbersOptions, InternalEditorScrollbarOptions, RenderLineNumbersType, RenderMinimap } from 'vs/editor/common/config/editorOptions'; interface IEditorLayoutProviderOpts { readonly outerWidth: number; @@ -39,6 +40,8 @@ interface IEditorLayoutProviderOpts { suite('Editor ViewLayout - EditorLayoutProvider', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function doTest(input: IEditorLayoutProviderOpts, expected: EditorLayoutInfo): void { const options = new ComputedEditorOptions(); options._write(EditorOption.glyphMargin, input.showGlyphMargin); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index a2ec4986989..854346ddfea 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -4,30 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreEditingCommands, CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; 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 { Selection } from 'vs/editor/common/core/selection'; +import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; -import { StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { IndentAction, IndentationRule } from 'vs/editor/common/languages/languageConfiguration'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; -import { withTestCodeEditor, TestCodeEditorInstantiationOptions, ITestCodeEditor, createCodeEditorServices, instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { EndOfLinePreference, EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { OutgoingViewModelEventKind } from 'vs/editor/common/viewModelEventDispatcher'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { ITestCodeEditor, TestCodeEditorInstantiationOptions, createCodeEditorServices, instantiateTestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; +import { IRelaxedTextModelCreationOptions, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { URI } from 'vs/base/common/uri'; // --------- utils @@ -142,6 +143,8 @@ suite('Editor Controller - Cursor', () => { }); } + ensureNoDisposablesAreLeakedInTestSuite(); + test('cursor initialized', () => { runTest((editor, viewModel) => { assertCursor(viewModel, new Position(1, 1)); @@ -833,18 +836,20 @@ suite('Editor Controller - Cursor', () => { // --------- eventing test('no move doesn\'t trigger event', () => { + runTest((editor, viewModel) => { - viewModel.onEvent((e) => { + const disposable = viewModel.onEvent((e) => { assert.ok(false, 'was not expecting event'); }); moveTo(editor, viewModel, 1, 1); + disposable.dispose(); }); }); test('move eventing', () => { runTest((editor, viewModel) => { let events = 0; - viewModel.onEvent((e) => { + const disposable = viewModel.onEvent((e) => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; assert.deepStrictEqual(e.selections, [new Selection(1, 2, 1, 2)]); @@ -852,13 +857,14 @@ suite('Editor Controller - Cursor', () => { }); moveTo(editor, viewModel, 1, 2); assert.strictEqual(events, 1, 'receives 1 event'); + disposable.dispose(); }); }); test('move in selection mode eventing', () => { runTest((editor, viewModel) => { let events = 0; - viewModel.onEvent((e) => { + const disposable = viewModel.onEvent((e) => { if (e.kind === OutgoingViewModelEventKind.CursorStateChanged) { events++; assert.deepStrictEqual(e.selections, [new Selection(1, 1, 1, 2)]); @@ -866,6 +872,7 @@ suite('Editor Controller - Cursor', () => { }); moveTo(editor, viewModel, 1, 2, true); assert.strictEqual(events, 1, 'receives 1 event'); + disposable.dispose(); }); }); @@ -1337,7 +1344,7 @@ suite('Editor Controller - Cursor', () => { withTestCodeEditor(model, {}, (editor1, cursor1) => { let event: ICursorPositionChangedEvent | undefined = undefined; - editor1.onDidChangeCursorPosition(e => { + const disposable = editor1.onDidChangeCursorPosition(e => { event = e; }); @@ -1347,6 +1354,7 @@ suite('Editor Controller - Cursor', () => { event = undefined; editor1.setPosition(new Position(1, 2), 'navigation'); assert.strictEqual(event!.source, 'navigation'); + disposable.dispose(); }); languageRegistration.dispose(); @@ -1403,6 +1411,8 @@ suite('Editor Controller', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + function setupOnEnterLanguage(indentAction: IndentAction): string { const onEnterLanguageId = 'onEnterMode'; @@ -2210,7 +2220,7 @@ suite('Editor Controller', () => { moveTo(editor, viewModel, 3, 4, true); let isFirst = true; - model.onDidChangeContent(() => { + const disposable = model.onDidChangeContent(() => { if (isFirst) { isFirst = false; viewModel.type('\t', 'keyboard'); @@ -2242,6 +2252,8 @@ suite('Editor Controller', () => { 'and more lines', 'just some text', ].join('\n'), '004'); + + disposable.dispose(); }); }); @@ -2726,11 +2738,13 @@ suite('Editor Controller', () => { withTestCodeEditor(model, {}, (editor1, cursor1) => { withTestCodeEditor(model, {}, (editor2, cursor2) => { - editor1.onDidChangeCursorPosition(() => { + const disposable = editor1.onDidChangeCursorPosition(() => { model.tokenization.tokenizeIfCheap(1); }); model.applyEdits([{ range: new Range(1, 1, 1, 1), text: '-' }]); + + disposable.dispose(); }); }); @@ -4862,12 +4876,13 @@ suite('Editor Controller', () => { }, (editor, model, viewModel) => { moveTo(editor, viewModel, 1, 5); let changeText: string | null = null; - model.onDidChangeContent(e => { + const disposable = model.onDidChangeContent(e => { changeText = e.changes[0].text; }); viewModel.type(')', 'keyboard'); assert.deepStrictEqual(model.getLineContent(1), '(div)'); assert.deepStrictEqual(changeText, ')'); + disposable.dispose(); }); }); @@ -6170,6 +6185,8 @@ suite('Editor Controller', () => { suite('Undo stops', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('there is an undo stop between typing and deleting left', () => { const model = createTextModel( [ diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 62d00dc365b..2e312c53e3e 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -4,16 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CoreNavigationCommands } from 'vs/editor/browser/coreCommands'; -import { CursorMove } from 'vs/editor/common/cursor/cursorMoveCommands'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { withTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { CursorMove } from 'vs/editor/common/cursor/cursorMoveCommands'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; +import { ITestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('Cursor move command test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const TEXT = [ ' \tMy First Line\t ', '\tMy Second Line', @@ -418,6 +421,8 @@ suite('Cursor move command test', () => { suite('Cursor move by blankline test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const TEXT = [ ' \tMy First Line\t ', '\tMy Second Line', diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index d07d12f085f..f66d1bbaa65 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInputHost, TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; import { TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; @@ -14,6 +15,8 @@ import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/tes suite('TextAreaInput', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + interface OutgoingType { type: 'type'; text: string; @@ -229,6 +232,8 @@ suite('TextAreaInput', () => { await yieldNow(); } + disposables.dispose(); + return outgoingEvents; } diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index c2634b71f2b..087e99311f2 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -71,6 +72,8 @@ function equalsTextAreaState(a: TextAreaState, b: TextAreaState): boolean { suite('TextAreaState', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertTextAreaState(actual: TextAreaState, value: string, selectionStart: number, selectionEnd: number): void { const desired = new TextAreaState(value, selectionStart, selectionEnd, null, undefined); assert.ok(equalsTextAreaState(desired, actual), desired.toString() + ' == ' + actual.toString()); diff --git a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts index f960df1acfc..ab0d682a7be 100644 --- a/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts +++ b/src/vs/editor/test/browser/view/minimapCharRenderer.test.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { RGBA8 } from 'vs/editor/common/core/rgba'; -import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MinimapCharRendererFactory } from 'vs/editor/browser/viewParts/minimap/minimapCharRendererFactory'; +import { Constants } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; +import { RGBA8 } from 'vs/editor/common/core/rgba'; suite('MinimapCharRenderer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const sampleD = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, diff --git a/src/vs/editor/test/browser/view/viewLayer.test.ts b/src/vs/editor/test/browser/view/viewLayer.test.ts index e7700d24293..0fcb0b1d57d 100644 --- a/src/vs/editor/test/browser/view/viewLayer.test.ts +++ b/src/vs/editor/test/browser/view/viewLayer.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; class TestLine implements ILine { @@ -41,6 +42,8 @@ function assertState(col: RenderedLinesCollection, state: ILinesCollec suite('RenderedLinesCollection onLinesDeleted', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testOnModelLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number, expectedDeleted: string[], expectedState: ILinesCollectionState): void { const col = new RenderedLinesCollection(() => new TestLine('new')); col._set(6, [ @@ -316,6 +319,8 @@ suite('RenderedLinesCollection onLinesDeleted', () => { suite('RenderedLinesCollection onLineChanged', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testOnModelLineChanged(changedLineNumber: number, expectedPinged: boolean, expectedState: ILinesCollectionState): void { const col = new RenderedLinesCollection(() => new TestLine('new')); col._set(6, [ @@ -397,6 +402,8 @@ suite('RenderedLinesCollection onLineChanged', () => { suite('RenderedLinesCollection onLinesInserted', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testOnModelLinesInserted(insertFromLineNumber: number, insertToLineNumber: number, expectedDeleted: string[], expectedState: ILinesCollectionState): void { const col = new RenderedLinesCollection(() => new TestLine('new')); col._set(6, [ @@ -673,6 +680,8 @@ suite('RenderedLinesCollection onLinesInserted', () => { suite('RenderedLinesCollection onTokensChanged', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testOnModelTokensChanged(changedFromLineNumber: number, changedToLineNumber: number, expectedPinged: boolean, expectedState: ILinesCollectionState): void { const col = new RenderedLinesCollection(() => new TestLine('new')); col._set(6, [ diff --git a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts index 530ae84b562..0974ab8d183 100644 --- a/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts +++ b/src/vs/editor/test/browser/viewModel/modelLineProjection.test.ts @@ -5,24 +5,28 @@ import * as assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { EndOfLinePreference } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import * as languages from 'vs/editor/common/languages'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; +import { EndOfLinePreference } from 'vs/editor/common/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData'; +import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { ViewLineData } from 'vs/editor/common/viewModel'; +import { IModelLineProjection, ISimpleModel, createModelLineProjection } from 'vs/editor/common/viewModel/modelLineProjection'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; import { ViewModelLinesFromProjectedModel } from 'vs/editor/common/viewModel/viewModelLines'; -import { ViewLineData } from 'vs/editor/common/viewModel'; import { TestConfiguration } from 'vs/editor/test/browser/config/testConfiguration'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { ISimpleModel, IModelLineProjection, createModelLineProjection } from 'vs/editor/common/viewModel/modelLineProjection'; -import { ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData'; -import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; suite('Editor ViewModel - SplitLinesCollection', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('SplitLine', () => { let model1 = createModel('My First LineMy Second LineAnd another one'); let line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 0); @@ -362,6 +366,7 @@ suite('SplitLinesCollection', () => { languageRegistration.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); interface ITestViewLineToken { endIndex: number; diff --git a/src/vs/editor/test/browser/viewModel/testViewModel.ts b/src/vs/editor/test/browser/viewModel/testViewModel.ts index 4933ac1a998..fe7f0c6e2db 100644 --- a/src/vs/editor/test/browser/viewModel/testViewModel.ts +++ b/src/vs/editor/test/browser/viewModel/testViewModel.ts @@ -18,7 +18,8 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: const configuration = new TestConfiguration(options); const model = createTextModel(text.join('\n')); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); - const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!, new TestLanguageConfigurationService(), new TestThemeService(), { + const testLanguageConfigurationService = new TestLanguageConfigurationService(); + const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!, testLanguageConfigurationService, new TestThemeService(), { setVisibleLines(visibleLines, stabilized) { }, }); @@ -28,4 +29,5 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: viewModel.dispose(); model.dispose(); configuration.dispose(); + testLanguageConfigurationService.dispose(); } diff --git a/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts b/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts index 9a057ea0ec3..02786c056b3 100644 --- a/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts +++ b/src/vs/editor/test/browser/viewModel/viewModelDecorations.test.ts @@ -4,12 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; import { testViewModel } from 'vs/editor/test/browser/viewModel/testViewModel'; suite('ViewModelDecorations', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('getDecorationsViewportData', () => { const text = [ 'hello world, this is a buffer that will be wrapped' diff --git a/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts index ff7515d2913..0f602b4e95e 100644 --- a/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/browser/viewModel/viewModelImpl.test.ts @@ -4,15 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, PositionAffinity } from 'vs/editor/common/model'; -import { testViewModel } from 'vs/editor/test/browser/viewModel/testViewModel'; import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import { ViewEvent } from 'vs/editor/common/viewEvents'; -import { Position } from 'vs/editor/common/core/position'; +import { testViewModel } from 'vs/editor/test/browser/viewModel/testViewModel'; suite('ViewModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #21073: SplitLinesCollection: attempt to access a \'newer\' model', () => { const text = ['']; const opts = { @@ -66,16 +69,20 @@ suite('ViewModel', () => { const viewLineCount: number[] = []; viewLineCount.push(viewModel.getLineCount()); - viewModel.addViewEventHandler(new class extends ViewEventHandler { + const eventHandler = new class extends ViewEventHandler { override handleEvents(events: ViewEvent[]): void { // Access the view model viewLineCount.push(viewModel.getLineCount()); } - }); + }; + viewModel.addViewEventHandler(eventHandler); model.undo(); viewLineCount.push(viewModel.getLineCount()); assert.deepStrictEqual(viewLineCount, [4, 1, 1, 1, 1]); + + viewModel.removeViewEventHandler(eventHandler); + eventHandler.dispose(); }); }); diff --git a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts index 43a058e3b74..192b4b6d8e4 100644 --- a/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts +++ b/src/vs/editor/test/browser/widget/codeEditorWidget.test.ts @@ -5,14 +5,17 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Selection } from 'vs/editor/common/core/selection'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; -import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { Selection } from 'vs/editor/common/core/selection'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; suite('CodeEditorWidget', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('onDidChangeModelDecorations', () => { withTestCodeEditor('', {}, (editor, viewModel) => { const disposables = new DisposableStore(); diff --git a/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts b/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts index ad354a95740..1f08e089c2e 100644 --- a/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts +++ b/src/vs/editor/test/browser/widget/diffEditorWidget.test.ts @@ -4,11 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; suite('DiffEditorWidget2', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + suite('UnchangedRegion', () => { function serialize(regions: UnchangedRegion[]): unknown { return regions.map(r => `${r.originalUnchangedRange} - ${r.modifiedUnchangedRange}`); diff --git a/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts b/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts index 7d579689ebc..200cb5e226a 100644 --- a/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts +++ b/src/vs/editor/test/common/controller/cursorAtomicMoveOperations.test.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/cursor/cursorAtomicMoveOperations'; suite('Cursor move command test', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Test whitespaceVisibleColumn', () => { const testCases = [ { diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index 3d873862937..c2d8dc85a45 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; suite('CursorMove', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('nextRenderTabStop', () => { assert.strictEqual(CursorColumns.nextRenderTabStop(0, 4), 4); assert.strictEqual(CursorColumns.nextRenderTabStop(1, 4), 4); diff --git a/src/vs/editor/test/common/core/characterClassifier.test.ts b/src/vs/editor/test/common/core/characterClassifier.test.ts index 774dcdf6086..dd4c4b02df3 100644 --- a/src/vs/editor/test/common/core/characterClassifier.test.ts +++ b/src/vs/editor/test/common/core/characterClassifier.test.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier'; suite('CharacterClassifier', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('works', () => { const classifier = new CharacterClassifier(0); diff --git a/src/vs/editor/test/common/core/lineRange.test.ts b/src/vs/editor/test/common/core/lineRange.test.ts index 919efee77a5..b67b9bdfb7b 100644 --- a/src/vs/editor/test/common/core/lineRange.test.ts +++ b/src/vs/editor/test/common/core/lineRange.test.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LineRange, LineRangeSet } from 'vs/editor/common/core/lineRange'; suite('LineRange', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('contains', () => { const r = new LineRange(2, 3); assert.deepStrictEqual(r.contains(1), false); @@ -17,6 +21,9 @@ suite('LineRange', () => { }); suite('LineRangeSet', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('addRange', () => { const set = new LineRangeSet(); set.addRange(new LineRange(2, 3)); diff --git a/src/vs/editor/test/common/core/lineTokens.test.ts b/src/vs/editor/test/common/core/lineTokens.test.ts index d7295ee74ea..177e66774df 100644 --- a/src/vs/editor/test/common/core/lineTokens.test.ts +++ b/src/vs/editor/test/common/core/lineTokens.test.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IViewLineTokens, LineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; +import { IViewLineTokens, LineTokens } from 'vs/editor/common/tokens/lineTokens'; suite('LineTokens', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + interface ILineToken { startIndex: number; foreground: number; diff --git a/src/vs/editor/test/common/core/range.test.ts b/src/vs/editor/test/common/core/range.test.ts index 4ab67c47ffb..bf574592d4e 100644 --- a/src/vs/editor/test/common/core/range.test.ts +++ b/src/vs/editor/test/common/core/range.test.ts @@ -3,10 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; suite('Editor Core - Range', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('empty range', () => { const s = new Range(1, 1, 1, 1); assert.strictEqual(s.startLineNumber, 1); diff --git a/src/vs/editor/test/common/core/stringBuilder.test.ts b/src/vs/editor/test/common/core/stringBuilder.test.ts index 88d217b0e61..af90a1f5eb1 100644 --- a/src/vs/editor/test/common/core/stringBuilder.test.ts +++ b/src/vs/editor/test/common/core/stringBuilder.test.ts @@ -6,10 +6,13 @@ import * as assert from 'assert'; import { writeUInt16LE } from 'vs/base/common/buffer'; import { CharCode } from 'vs/base/common/charCode'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { decodeUTF16LE, StringBuilder } from 'vs/editor/common/core/stringBuilder'; suite('decodeUTF16LE', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #118041: unicode character undo bug 1', () => { const buff = new Uint8Array(2); writeUInt16LE(buff, ''.charCodeAt(0), 0); @@ -37,6 +40,9 @@ suite('decodeUTF16LE', () => { }); suite('StringBuilder', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('basic', () => { const sb = new StringBuilder(100); sb.appendASCIICharCode(CharCode.A); diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index aca599dc6e5..38378ef3826 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { Constants } from 'vs/base/common/uint'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { DiffComputer, ICharChange, ILineChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; @@ -223,6 +224,8 @@ function createCharChange( suite('Editor Diff - DiffComputer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + // ---- insertions test('one inserted line below', () => { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts index 609866a8bf5..264292f82ff 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts @@ -5,12 +5,16 @@ import * as assert from 'assert'; import { splitLines } from 'vs/base/common/strings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Single-Line 1', () => { assert.deepStrictEqual( compute( diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts index 619d10d52ec..5ab26b65dff 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; @@ -13,6 +14,9 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; suite('combineTextEditInfos', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + for (let seed = 0; seed < 50; seed++) { test('test' + seed, () => { runTest(seed); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts index c0c81ff2096..42d0eae8f81 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/concat23Trees.test.ts @@ -4,11 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast'; -import { toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { concat23Trees } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees'; +import { toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; suite('Bracket Pair Colorizer - mergeItems', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Clone', () => { const tree = ListAstNode.create([ new TextAstNode(toLength(1, 1)), diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts index 309d76c5cc6..09c9e34a124 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts @@ -5,19 +5,22 @@ import * as assert from 'assert'; import { DisposableStore, disposeOnReturn } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { BracketPairInfo } from 'vs/editor/common/textModelBracketPairs'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { TextModel } from 'vs/editor/common/model/textModel'; -import { TokenInfo, TokenizedDocument } from 'vs/editor/test/common/model/bracketPairColorizer/tokenizer.test'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { BracketPairInfo } from 'vs/editor/common/textModelBracketPairs'; +import { TokenInfo, TokenizedDocument } from 'vs/editor/test/common/model/bracketPairColorizer/tokenizer.test'; +import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function createTextModelWithColorizedBracketPairs(store: DisposableStore, text: string): TextModel { const languageId = 'testLanguage'; const instantiationService = createModelServices(store); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts index b1932043ddd..eb49c1048d1 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; suite('Bracket Pair Colorizer - Length', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function toStr(length: Length): string { return lengthToObj(length).toString(); } diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts index de3168139c5..45d4dcc6833 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/smallImmutableSet.test.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; suite('Bracket Pair Colorizer - ImmutableSet', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Basic', () => { const keyProvider = new DenseKeyProvider(); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index ca5535c0eb5..a83b30a66eb 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -5,18 +5,22 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { LanguageId, MetadataConsts, StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets'; import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet'; import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { EncodedTokenizationResult, IState, ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/languages'; -import { LanguageId, StandardTokenType, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; -import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; -import { ILanguageService } from 'vs/editor/common/languages/language'; import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; suite('Bracket Pair Colorizer - Tokenizer', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Basic', () => { const mode1 = 'testMode1'; const disposableStore = new DisposableStore(); diff --git a/src/vs/editor/test/common/model/editStack.test.ts b/src/vs/editor/test/common/model/editStack.test.ts index 27cb5864613..d220d12e668 100644 --- a/src/vs/editor/test/common/model/editStack.test.ts +++ b/src/vs/editor/test/common/model/editStack.test.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EndOfLineSequence } from 'vs/editor/common/model'; -import { SingleModelEditStackData } from 'vs/editor/common/model/editStack'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Selection } from 'vs/editor/common/core/selection'; import { TextChange } from 'vs/editor/common/core/textChange'; +import { EndOfLineSequence } from 'vs/editor/common/model'; +import { SingleModelEditStackData } from 'vs/editor/common/model/editStack'; suite('EditStack', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #118041: unicode character undo bug', () => { const stackData = new SingleModelEditStackData( 1, diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index a120227b45d..aa39ad03e8c 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLinePreference, EndOfLineSequence } from 'vs/editor/common/model'; @@ -14,6 +16,8 @@ import { createTextModel } from 'vs/editor/test/common/testTextModel'; suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testApplyEdits(original: string[], edits: ISingleEditOperation[], before: boolean, after: boolean): void { const model = createTextModel(original.join('\n')); model.setEOL(EndOfLineSequence.LF); @@ -60,6 +64,8 @@ suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () = suite('EditorModel - EditableTextModel.applyEdits updates mightContainNonBasicASCII', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testApplyEdits(original: string[], edits: ISingleEditOperation[], before: boolean, after: boolean): void { const model = createTextModel(original.join('\n')); model.setEOL(EndOfLineSequence.LF); @@ -102,6 +108,8 @@ suite('EditorModel - EditableTextModel.applyEdits updates mightContainNonBasicAS suite('EditorModel - EditableTextModel.applyEdits', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation { return { range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), @@ -205,7 +213,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { ); }); - test('Bug 19872: Undo is funky', () => { + test('Bug 19872: Undo is funky (2)', () => { testApplyEditsWithSyncedModels( [ 'something', @@ -981,7 +989,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }); test('change while emitting events 1', () => { - + let disposable!: IDisposable; assertSyncedModels('Hello', (model, assertMirrorModels) => { model.applyEdits([{ range: new Range(1, 6, 1, 6), @@ -993,7 +1001,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }, (model) => { let isFirstTime = true; - model.onDidChangeContent(() => { + disposable = model.onDidChangeContent(() => { if (!isFirstTime) { return; } @@ -1006,10 +1014,11 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }]); }); }); + disposable.dispose(); }); test('change while emitting events 2', () => { - + let disposable!: IDisposable; assertSyncedModels('Hello', (model, assertMirrorModels) => { model.applyEdits([{ range: new Range(1, 6, 1, 6), @@ -1021,7 +1030,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }, (model) => { let isFirstTime = true; - model.onDidChangeContent((e: IModelContentChangedEvent) => { + disposable = model.onDidChangeContent((e: IModelContentChangedEvent) => { if (!isFirstTime) { return; } @@ -1034,6 +1043,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { }]); }); }); + disposable.dispose(); }); test('issue #1580: Changes in line endings are not correctly reflected in the extension host, leading to invalid offsets sent to external refactoring tools', () => { @@ -1043,7 +1053,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { const mirrorModel2 = new MirrorTextModel(null!, model.getLinesContent(), model.getEOL(), model.getVersionId()); let mirrorModel2PrevVersionId = model.getVersionId(); - model.onDidChangeContent((e: IModelContentChangedEvent) => { + const disposable = model.onDidChangeContent((e: IModelContentChangedEvent) => { const versionId = e.versionId; if (versionId < mirrorModel2PrevVersionId) { console.warn('Model version id did not advance between edits (2)'); @@ -1060,6 +1070,7 @@ suite('EditorModel - EditableTextModel.applyEdits', () => { model.setEOL(EndOfLineSequence.CRLF); assertMirrorModels(); + disposable.dispose(); model.dispose(); mirrorModel2.dispose(); }); diff --git a/src/vs/editor/test/common/model/editableTextModelAuto.test.ts b/src/vs/editor/test/common/model/editableTextModelAuto.test.ts index f7e6a146d1e..31e118bbc82 100644 --- a/src/vs/editor/test/common/model/editableTextModelAuto.test.ts +++ b/src/vs/editor/test/common/model/editableTextModelAuto.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -12,6 +13,9 @@ import { testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/edit const GENERATE_TESTS = false; suite('EditorModel Auto Tests', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[]): ISingleEditOperation { return { range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index 658ea7a1c9c..06803315a7b 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -100,7 +100,7 @@ export function assertSyncedModels(text: string, callback: (model: TextModel, as const mirrorModel2 = new MirrorTextModel(null!, model.getLinesContent(), model.getEOL(), model.getVersionId()); let mirrorModel2PrevVersionId = model.getVersionId(); - model.onDidChangeContent((e: IModelContentChangedEvent) => { + const disposable = model.onDidChangeContent((e: IModelContentChangedEvent) => { const versionId = e.versionId; if (versionId < mirrorModel2PrevVersionId) { console.warn('Model version id did not advance between edits (2)'); @@ -117,6 +117,7 @@ export function assertSyncedModels(text: string, callback: (model: TextModel, as callback(model, assertMirrorModels); + disposable.dispose(); model.dispose(); mirrorModel2.dispose(); } diff --git a/src/vs/editor/test/common/model/intervalTree.test.ts b/src/vs/editor/test/common/model/intervalTree.test.ts index bb94b0db456..06534b6e2a6 100644 --- a/src/vs/editor/test/common/model/intervalTree.test.ts +++ b/src/vs/editor/test/common/model/intervalTree.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; import { IntervalNode, IntervalTree, NodeColor, SENTINEL, getNodeColor, intervalCompare, nodeAcceptEdit, setNodeStickiness } from 'vs/editor/common/model/intervalTree'; @@ -19,6 +20,8 @@ const MAX_CHANGE_CNT = 20; suite('IntervalTree 1', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + class Interval { _intervalBrand: void = undefined; @@ -555,6 +558,9 @@ suite('IntervalTree 1', () => { }); suite('IntervalTree 2', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function assertNodeAcceptEdit(msg: string, nodeStart: number, nodeEnd: number, nodeStickiness: TrackedRangeStickiness, start: number, end: number, textLength: number, forceMoveMarkers: boolean, expectedNodeStart: number, expectedNodeEnd: number): void { const node = new IntervalNode('', nodeStart, nodeEnd); setNodeStickiness(node, nodeStickiness); diff --git a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts index c762f00593a..77bc1e74cc1 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBuffer.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { IValidatedEditOperation, PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; @@ -11,6 +12,8 @@ import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; suite('PieceTreeTextBuffer._getInverseEdits', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, text: string[] | null): IValidatedEditOperation { return { sortIndex: 0, @@ -265,6 +268,8 @@ suite('PieceTreeTextBuffer._getInverseEdits', () => { suite('PieceTreeTextBuffer._toSingleEditOperation', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function editOp(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeOffset: number, rangeLength: number, text: string[] | null): IValidatedEditOperation { return { sortIndex: 0, @@ -282,10 +287,11 @@ suite('PieceTreeTextBuffer._toSingleEditOperation', () => { } function testToSingleEditOperation(original: string[], edits: IValidatedEditOperation[], expected: IValidatedEditOperation): void { - const textBuffer = createTextBufferFactory(original.join('\n')).create(DefaultEndOfLine.LF).textBuffer; + const { disposable, textBuffer } = createTextBufferFactory(original.join('\n')).create(DefaultEndOfLine.LF); - const actual = textBuffer._toSingleEditOperation(edits); + const actual = (textBuffer)._toSingleEditOperation(edits); assert.deepStrictEqual(actual, expected); + disposable.dispose(); } test('one edit op is unchanged', () => { diff --git a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts index 0d97a198176..22f182090b8 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts @@ -5,20 +5,23 @@ import * as assert from 'assert'; import * as strings from 'vs/base/common/strings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; function testTextBufferFactory(text: string, eol: string, mightContainNonBasicASCII: boolean, mightContainRTL: boolean): void { - const textBuffer = createTextBufferFactory(text).create(DefaultEndOfLine.LF).textBuffer; + const { disposable, textBuffer } = createTextBufferFactory(text).create(DefaultEndOfLine.LF); assert.strictEqual(textBuffer.mightContainNonBasicASCII(), mightContainNonBasicASCII); assert.strictEqual(textBuffer.mightContainRTL(), mightContainRTL); assert.strictEqual(textBuffer.getEOL(), eol); + disposable.dispose(); } suite('ModelBuilder', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('t1', () => { testTextBufferFactory('', '\n', false, false); }); @@ -51,10 +54,6 @@ suite('ModelBuilder', () => { testTextBufferFactory(strings.UTF8_BOM_CHARACTER + 'Hello world!', '\n', false, false); }); - test('BOM handling', () => { - testTextBufferFactory(strings.UTF8_BOM_CHARACTER + 'Hello world!', '\n', false, false); - }); - test('RTL handling 2', () => { testTextBufferFactory('Hello world!זוהי עובדה מבוססת שדעתו', '\n', true, true); }); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index d011ee22f05..5eb7462e045 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -4,15 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; -import { computeIndentLevel } from 'vs/editor/common/model/utils'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { EncodedTokenizationResult, IBackgroundTokenizationStore, IBackgroundTokenizer, IState, ITokenizationSupport, TokenizationRegistry, TokenizationResult } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { computeIndentLevel } from 'vs/editor/common/model/utils'; +import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder'; +import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { TestLineToken, TestLineTokenFactory } from 'vs/editor/test/common/core/testLineToken'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { ITokenizationSupport, TokenizationRegistry, IState, IBackgroundTokenizationStore, EncodedTokenizationResult, TokenizationResult, IBackgroundTokenizer } from 'vs/editor/common/languages'; -import { ITextModel } from 'vs/editor/common/model'; -import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder'; interface ILineEdit { startColumn: number; @@ -46,6 +47,9 @@ function assertLineTokens(__actual: LineTokens, _expected: TestToken[]): void { } suite('ModelLine - getIndentLevel', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function assertIndentLevel(text: string, expected: number, tabSize: number = 4): void { const actual = computeIndentLevel(text, tabSize); assert.strictEqual(actual, expected, text); @@ -148,6 +152,8 @@ class LineState implements IState { suite('ModelLinesTokens', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + interface IBufferLineState { text: string; tokens: TestToken[]; diff --git a/src/vs/editor/test/common/model/model.modes.test.ts b/src/vs/editor/test/common/model/model.modes.test.ts index dfefb300423..fa48350404f 100644 --- a/src/vs/editor/test/common/model/model.modes.test.ts +++ b/src/vs/editor/test/common/model/model.modes.test.ts @@ -5,12 +5,13 @@ import * as assert from 'assert'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; 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 { TextModel } from 'vs/editor/common/model/textModel'; import * as languages from 'vs/editor/common/languages'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; +import { TextModel } from 'vs/editor/common/model/textModel'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; // --------- utils @@ -56,6 +57,8 @@ suite('Editor Model - Model Modes 1', () => { calledFor = []; }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('model calls syntax highlighter 1', () => { thisModel.tokenization.forceTokenization(1); assert.deepStrictEqual(getAndClear(), ['1']); @@ -209,6 +212,8 @@ suite('Editor Model - Model Modes 2', () => { languageRegistration.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('getTokensForInvalidLines one text insert', () => { thisModel.tokenization.forceTokenization(5); assert.deepStrictEqual(getAndClear(), ['Line1', 'Line2', 'Line3', 'Line4', 'Line5']); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 9039d6d6b4c..a75e86e7b59 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -5,17 +5,18 @@ import * as assert from 'assert'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; 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 { TextModel } from 'vs/editor/common/model/textModel'; -import { InternalModelContentChangeEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents'; -import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; +import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { NullState } from 'vs/editor/common/languages/nullTokenize'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { InternalModelContentChangeEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/textModelEvents'; import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { ILanguageService } from 'vs/editor/common/languages/language'; // --------- utils @@ -43,6 +44,8 @@ suite('Editor Model - Model', () => { thisModel.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + // --------- insert text test('model getValue', () => { @@ -96,15 +99,16 @@ suite('Editor Model - Model', () => { // --------- insert text eventing test('model insert empty text does not trigger eventing', () => { - thisModel.onDidChangeContentOrInjectedText((e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((e) => { assert.ok(false, 'was not expecting event'); }); thisModel.applyEdits([EditOperation.insert(new Position(1, 1), '')]); + disposable.dispose(); }); test('model insert text without newline eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -119,11 +123,12 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); test('model insert text with one newline eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -139,6 +144,7 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); @@ -192,15 +198,16 @@ suite('Editor Model - Model', () => { // --------- delete text eventing test('model delete empty text does not trigger eventing', () => { - thisModel.onDidChangeContentOrInjectedText((e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((e) => { assert.ok(false, 'was not expecting event'); }); thisModel.applyEdits([EditOperation.delete(new Range(1, 1, 1, 1))]); + disposable.dispose(); }); test('model delete text from one line eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -215,11 +222,12 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); test('model delete all text from a line eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -234,11 +242,12 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); test('model delete text from two lines eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -254,11 +263,12 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); test('model delete text from many lines eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -274,6 +284,7 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); // --------- getValueInRange @@ -309,7 +320,7 @@ suite('Editor Model - Model', () => { // --------- setValue test('setValue eventing', () => { let e: ModelRawContentChangedEvent | null = null; - thisModel.onDidChangeContentOrInjectedText((_e) => { + const disposable = thisModel.onDidChangeContentOrInjectedText((_e) => { if (e !== null || !(_e instanceof InternalModelContentChangeEvent)) { assert.fail('Unexpected assertion error'); } @@ -324,6 +335,7 @@ suite('Editor Model - Model', () => { false, false )); + disposable.dispose(); }); test('issue #46342: Maintain edit operation order in applyEdits', () => { @@ -357,6 +369,8 @@ suite('Editor Model - Model Line Separators', () => { thisModel.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('model getValue', () => { assert.strictEqual(thisModel.getValue(), 'My First Line\u2028\t\tMy Second Line\n Third Line\u2028\n1'); }); @@ -445,6 +459,8 @@ suite('Editor Model - Words', () => { disposables = []; }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('Get word at position', () => { const text = ['This text has some words. ']; const thisModel = createTextModel(text.join('\n')); diff --git a/src/vs/editor/test/common/model/modelDecorations.test.ts b/src/vs/editor/test/common/model/modelDecorations.test.ts index cb517d5371e..dda14417b1a 100644 --- a/src/vs/editor/test/common/model/modelDecorations.test.ts +++ b/src/vs/editor/test/common/model/modelDecorations.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -101,6 +102,8 @@ suite('Editor Model - Model Decorations', () => { thisModel.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('single character decoration', () => { addDecoration(thisModel, 1, 1, 1, 2, 'myType'); lineHasDecoration(thisModel, 1, 1, 2, 'myType'); @@ -206,53 +209,57 @@ suite('Editor Model - Model Decorations', () => { test('decorations emit event on add', () => { let listenerCalled = 0; - thisModel.onDidChangeDecorations((e) => { + const disposable = thisModel.onDidChangeDecorations((e) => { listenerCalled++; }); addDecoration(thisModel, 1, 2, 3, 2, 'myType'); assert.strictEqual(listenerCalled, 1, 'listener called'); + disposable.dispose(); }); test('decorations emit event on change', () => { let listenerCalled = 0; const decId = addDecoration(thisModel, 1, 2, 3, 2, 'myType'); - thisModel.onDidChangeDecorations((e) => { + const disposable = thisModel.onDidChangeDecorations((e) => { listenerCalled++; }); thisModel.changeDecorations((changeAccessor) => { changeAccessor.changeDecoration(decId, new Range(1, 1, 1, 2)); }); assert.strictEqual(listenerCalled, 1, 'listener called'); + disposable.dispose(); }); test('decorations emit event on remove', () => { let listenerCalled = 0; const decId = addDecoration(thisModel, 1, 2, 3, 2, 'myType'); - thisModel.onDidChangeDecorations((e) => { + const disposable = thisModel.onDidChangeDecorations((e) => { listenerCalled++; }); thisModel.changeDecorations((changeAccessor) => { changeAccessor.removeDecoration(decId); }); assert.strictEqual(listenerCalled, 1, 'listener called'); + disposable.dispose(); }); test('decorations emit event when inserting one line text before it', () => { let listenerCalled = 0; addDecoration(thisModel, 1, 2, 3, 2, 'myType'); - thisModel.onDidChangeDecorations((e) => { + const disposable = thisModel.onDidChangeDecorations((e) => { listenerCalled++; }); thisModel.applyEdits([EditOperation.insert(new Position(1, 1), 'Hallo ')]); assert.strictEqual(listenerCalled, 1, 'listener called'); + disposable.dispose(); }); test('decorations do not emit event on no-op deltaDecorations', () => { let listenerCalled = 0; - thisModel.onDidChangeDecorations((e) => { + const disposable = thisModel.onDidChangeDecorations((e) => { listenerCalled++; }); @@ -262,6 +269,7 @@ suite('Editor Model - Model Decorations', () => { }); assert.strictEqual(listenerCalled, 0, 'listener not called'); + disposable.dispose(); }); // --------- editing text & effects on decorations @@ -416,6 +424,8 @@ suite('Editor Model - Model Decorations', () => { suite('Decorations and editing', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function _runTest(decRange: Range, stickiness: TrackedRangeStickiness, editRange: Range, editText: string, editForceMoveMarkers: boolean, expectedDecRange: Range, msg: string): void { const model = createTextModel([ 'My First Line', @@ -1113,6 +1123,8 @@ interface ILightWeightDecoration { suite('deltaDecorations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function decoration(id: string, startLineNumber: number, startColumn: number, endLineNumber: number, endColum: number): ILightWeightDecoration { return { id: id, diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index b4a6eb4ff75..a2cd24ce4f4 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -31,6 +32,8 @@ suite('Editor Model - Model Edit Operation', () => { model.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + function createSingleEditOp(text: string, positionLineNumber: number, positionColumn: number, selectionLineNumber: number = positionLineNumber, selectionColumn: number = positionColumn): ISingleEditOperation { const range = new Range( selectionLineNumber, diff --git a/src/vs/editor/test/common/model/textChange.test.ts b/src/vs/editor/test/common/model/textChange.test.ts index bb6976dd070..164e7ff92e1 100644 --- a/src/vs/editor/test/common/model/textChange.test.ts +++ b/src/vs/editor/test/common/model/textChange.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { compressConsecutiveTextChanges, TextChange } from 'vs/editor/common/core/textChange'; const GENERATE_TESTS = false; @@ -16,6 +17,8 @@ interface IGeneratedEdit { suite('TextChangeCompressor', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function getResultingContent(initialContent: string, edits: IGeneratedEdit[]): string { let content = initialContent; for (let i = edits.length - 1; i >= 0; i--) { @@ -283,6 +286,8 @@ suite('TextChangeCompressor', () => { suite('TextChange', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #118041: unicode character undo bug', () => { const textChange = new TextChange(428, '', 428, ''); const buff = new Uint8Array(textChange.writeSize()); diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index e060a9b4466..dc6037d6df4 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { UTF8_BOM_CHARACTER } from 'vs/base/common/strings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; @@ -72,6 +73,8 @@ function assertGuess(expectedInsertSpaces: boolean | undefined, expectedTabSize: suite('TextModelData.fromString', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + interface ITextBufferData { EOL: string; lines: string[]; @@ -80,7 +83,7 @@ suite('TextModelData.fromString', () => { } function testTextModelDataFromString(text: string, expected: ITextBufferData): void { - const textBuffer = createTextBuffer(text, TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL).textBuffer; + const { textBuffer, disposable } = createTextBuffer(text, TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL); const actual: ITextBufferData = { EOL: textBuffer.getEOL(), lines: textBuffer.getLinesContent(), @@ -88,6 +91,7 @@ suite('TextModelData.fromString', () => { isBasicASCII: !textBuffer.mightContainNonBasicASCII() }; assert.deepStrictEqual(actual, expected); + disposable.dispose(); } test('one line text', () => { @@ -166,6 +170,8 @@ suite('TextModelData.fromString', () => { suite('Editor Model - TextModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('TextModel does not use events internally', () => { // Make sure that all model parts receive text model events explicitly // to avoid that by any chance an outside listener receives events before @@ -1067,6 +1073,8 @@ suite('Editor Model - TextModel', () => { suite('TextModel.mightContainRTL', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('nope', () => { const model = createTextModel('hello world!'); assert.strictEqual(model.mightContainRTL(), false); @@ -1099,6 +1107,8 @@ suite('TextModel.mightContainRTL', () => { suite('TextModel.createSnapshot', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('empty file', () => { const model = createTextModel(''); const snapshot = model.createSnapshot(); diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index 555a1f5f59a..16d237c00a5 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -4,18 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getMapForWordSeparators } from 'vs/editor/common/core/wordCharacterClassifier'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { getMapForWordSeparators } from 'vs/editor/common/core/wordCharacterClassifier'; +import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; import { EndOfLineSequence, FindMatch, SearchData } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SearchParams, TextModelSearch, isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; -import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/core/wordHelper'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; // --------- Find suite('TextModelSearch', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const usualWordSeparators = getMapForWordSeparators(USUAL_WORD_SEPARATORS); function assertFindMatch(actual: FindMatch | null, expectedRange: Range, expectedMatches: string[] | null = null): void { diff --git a/src/vs/editor/test/common/model/textModelTokens.test.ts b/src/vs/editor/test/common/model/textModelTokens.test.ts index 3bcabeba1e0..b425cc30fbf 100644 --- a/src/vs/editor/test/common/model/textModelTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelTokens.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { RangePriorityQueueImpl } from 'vs/editor/common/model/textModelTokens'; suite('RangePriorityQueueImpl', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('addRange', () => { const ranges: OffsetRange[] = []; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 9406e15adf7..f2b4aa2b595 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -18,6 +18,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestLineToken } from 'vs/editor/test/common/core/testLineToken'; import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; function createTextModelWithBrackets(disposables: DisposableStore, text: string, brackets: CharacterPair[]): TextModel { const languageId = 'bracketMode2'; @@ -32,6 +33,9 @@ function createTextModelWithBrackets(disposables: DisposableStore, text: string, } suite('TextModelWithTokens', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function testBrackets(contents: string[], brackets: CharacterPair[]): void { const languageId = 'testMode'; const disposables = new DisposableStore(); @@ -194,6 +198,8 @@ suite('TextModelWithTokens - bracket matching', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('bracket matching 1', () => { const text = ')]}{[(' + '\n' + @@ -273,6 +279,8 @@ suite('TextModelWithTokens - bracket matching', () => { suite('TextModelWithTokens 2', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('bracket matching 3', () => { const text = [ 'begin', @@ -534,6 +542,8 @@ suite('TextModelWithTokens 2', () => { suite('TextModelWithTokens regression tests', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => { function assertViewLineTokens(model: TextModel, lineNumber: number, forceTokenization: boolean, expected: TestLineToken[]): void { if (forceTokenization) { @@ -699,6 +709,9 @@ suite('TextModelWithTokens regression tests', () => { }); suite('TextModel.getLineIndentGuide', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void { const languageId = 'testLang'; const disposables = new DisposableStore(); diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 114996c3ff2..7ceba9ef598 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -4,21 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { ColorId, FontStyle, MetadataConsts, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; +import { ILanguageConfigurationService, LanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; +import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore'; -import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { TextModel } from 'vs/editor/common/model/textModel'; -import { FontStyle, ColorId, MetadataConsts, TokenMetadata } from 'vs/editor/common/encodedTokenAttributes'; import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -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 { ILanguageConfigurationService, LanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; suite('TokensStore', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + const SEMANTIC_COLOR = 5 as ColorId; function parseTokensState(state: string[]): { text: string; tokens: SparseMultilineTokens } { @@ -253,7 +256,7 @@ suite('TokensStore', () => { const instantiationService = createModelServices(disposables, [ [ILanguageConfigurationService, LanguageConfigurationService] ]); - const model = instantiateTextModel(instantiationService, '--[[\n\n]]'); + const model = disposables.add(instantiateTextModel(instantiationService, '--[[\n\n]]')); model.tokenization.setSemanticTokens([ SparseMultilineTokens.create(1, new Uint32Array([ 0, 2, 4, 0b100000000000010000, diff --git a/src/vs/editor/test/common/modes/languageConfiguration.test.ts b/src/vs/editor/test/common/modes/languageConfiguration.test.ts index e7da368fc2f..97c74722cc9 100644 --- a/src/vs/editor/test/common/modes/languageConfiguration.test.ts +++ b/src/vs/editor/test/common/modes/languageConfiguration.test.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; suite('StandardAutoClosingPairConditional', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Missing notIn', () => { const v = new StandardAutoClosingPairConditional({ open: '{', close: '}' }); assert.strictEqual(v.isOK(StandardTokenType.Other), true); @@ -98,5 +101,6 @@ suite('StandardAutoClosingPairConditional', () => { assert.strictEqual(languageConfigurationService.getLanguageConfiguration(id).comments?.lineCommentToken, '1'); d1.dispose(); d2.dispose(); + languageConfigurationService.dispose(); }); }); diff --git a/src/vs/editor/test/common/modes/languageSelector.test.ts b/src/vs/editor/test/common/modes/languageSelector.test.ts index 5d699790958..7f90aa1d962 100644 --- a/src/vs/editor/test/common/modes/languageSelector.test.ts +++ b/src/vs/editor/test/common/modes/languageSelector.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguageSelector, score } from 'vs/editor/common/languageSelector'; suite('LanguageSelector', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + const model = { language: 'farboo', uri: URI.parse('file:///testbed/file.fb') diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index 4ddf17ba95a..49411db88e9 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILink } from 'vs/editor/common/languages'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer'; @@ -62,6 +63,8 @@ function assertLink(text: string, extractedLink: string): void { suite('Editor Modes - Link Computer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Null model', () => { const r = computeLinks(null); assert.deepStrictEqual(r, []); diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index c7a4a99218b..70c763ec16e 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; import { CharacterPairSupport } from 'vs/editor/common/languages/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; -import { StandardAutoClosingPairConditional } from 'vs/editor/common/languages/languageConfiguration'; suite('CharacterPairSupport', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('only autoClosingPairs', () => { const characaterPairSupport = new CharacterPairSupport({ autoClosingPairs: [{ open: 'a', close: 'b' }] }); assert.deepStrictEqual(characaterPairSupport.getAutoClosingPairs(), [new StandardAutoClosingPairConditional({ open: 'a', close: 'b' })]); diff --git a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts index f477d8b14da..90d89185aa4 100644 --- a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts +++ b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/languages/supports/electricCharacter'; import { RichEditBrackets } from 'vs/editor/common/languages/supports/richEditBrackets'; @@ -12,6 +13,9 @@ import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/mod const fakeLanguageId = 'test'; suite('Editor Modes - Auto Indentation', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + function _testOnElectricCharacter(electricCharacterSupport: BracketElectricCharacterSupport, line: TokenText[], character: string, offset: number): IElectricAction | null { return electricCharacterSupport.onElectricCharacter(character, createFakeScopedLineTokens(line), offset); } diff --git a/src/vs/editor/test/common/modes/supports/onEnter.test.ts b/src/vs/editor/test/common/modes/supports/onEnter.test.ts index 94f4fe24acc..44b1af8d341 100644 --- a/src/vs/editor/test/common/modes/supports/onEnter.test.ts +++ b/src/vs/editor/test/common/modes/supports/onEnter.test.ts @@ -7,9 +7,12 @@ import { CharacterPair, IndentAction } from 'vs/editor/common/languages/language import { OnEnterSupport } from 'vs/editor/common/languages/supports/onEnter'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('OnEnter', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('uses brackets', () => { const brackets: CharacterPair[] = [ ['(', ')'], diff --git a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts index 9953e76ccdb..f58f7105d7e 100644 --- a/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts +++ b/src/vs/editor/test/common/modes/supports/richEditBrackets.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { BracketsUtils } from 'vs/editor/common/languages/supports/richEditBrackets'; suite('richEditBrackets', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function findPrevBracketInRange(reversedBracketRegex: RegExp, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null { return BracketsUtils.findPrevBracketInRange(reversedBracketRegex, 1, lineText, currentTokenStart, currentTokenEnd); } diff --git a/src/vs/editor/test/common/modes/supports/tokenization.test.ts b/src/vs/editor/test/common/modes/supports/tokenization.test.ts index 56229b442f5..26854898f03 100644 --- a/src/vs/editor/test/common/modes/supports/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/supports/tokenization.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FontStyle } from 'vs/editor/common/encodedTokenAttributes'; import { ColorMap, ExternalThemeTrieElement, ParsedTokenThemeRule, ThemeTrieElementRule, TokenTheme, parseTokenTheme, strcmp } from 'vs/editor/common/languages/supports/tokenization'; suite('Token theme matching', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('gives higher priority to deeper matches', () => { const theme = TokenTheme.createFromRawTokenTheme([ { token: '', foreground: '100000', background: '200000' }, @@ -127,6 +130,8 @@ suite('Token theme matching', () => { suite('Token theme parsing', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('can parse', () => { const actual = parseTokenTheme([ @@ -163,6 +168,8 @@ suite('Token theme parsing', () => { suite('Token theme resolving', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('strcmp works', () => { const actual = ['bar', 'z', 'zu', 'a', 'ab', ''].sort(strcmp); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 391a54710f6..7593ea14706 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -5,10 +5,11 @@ import * as assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ColorId, FontStyle, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { EncodedTokenizationResult, IState, TokenizationRegistry } from 'vs/editor/common/languages'; -import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { tokenizeLineToHTML, _tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer'; +import { _tokenizeToString, tokenizeLineToHTML } from 'vs/editor/common/languages/textToHtmlTokenizer'; import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry'; import { TestLineToken, TestLineTokens } from 'vs/editor/test/common/core/testLineToken'; import { createModelServices } from 'vs/editor/test/common/testTextModel'; @@ -28,6 +29,8 @@ suite('Editor Modes - textToHtmlTokenizer', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + function toStr(pieces: { className: string; text: string }[]): string { const resultArr = pieces.map((t) => `${t.text}`); return resultArr.join(''); diff --git a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts index 9596c1225c6..15accef1657 100644 --- a/src/vs/editor/test/common/services/editorSimpleWorker.test.ts +++ b/src/vs/editor/test/common/services/editorSimpleWorker.test.ts @@ -4,14 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Position } from 'vs/editor/common/core/position'; -import { Range, IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { TextEdit } from 'vs/editor/common/languages'; import { EditorSimpleWorker, ICommonModel } from 'vs/editor/common/services/editorSimpleWorker'; import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; suite('EditorSimpleWorker', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + class WorkerWithModels extends EditorSimpleWorker { getModel(uri: string) { @@ -254,6 +257,15 @@ suite('EditorSimpleWorker', () => { ); }); + test.skip('[Bug] Getting Message "Overlapping ranges are not allowed" and nothing happens with Inline-Chat ', async function () { + await testEdits(("const API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const req = {};\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === '/books') {\n handler(req, res);\n }\n },\n });\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n author: mockAuthor,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/author/${mockAuthor}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const req = {\n params: {\n title: mockTitle,\n },\n };\n const res = {\n json: jest.fn(),\n };\n\n await api.register({\n get: (path, handler) => {\n if (path === `/books/title/${mockTitle}`) {\n handler(req, res);\n }\n },\n });\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(res.json).toHaveBeenCalledWith(mockBooks);\n });\n });\n});\n").split('\n'), + [{ + range: { startLineNumber: 1, startColumn: 1, endLineNumber: 96, endColumn: 1 }, + text: `const request = require('supertest');\nconst API = require('../src/api');\n\ndescribe('API', () => {\n let api;\n let database;\n\n beforeAll(() => {\n database = {\n getAllBooks: jest.fn(),\n getBooksByAuthor: jest.fn(),\n getBooksByTitle: jest.fn(),\n };\n api = new API(database);\n });\n\n describe('GET /books', () => {\n it('should return all books', async () => {\n const mockBooks = [{ title: 'Book 1' }, { title: 'Book 2' }];\n database.getAllBooks.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get('/books');\n\n expect(database.getAllBooks).toHaveBeenCalled();\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/author/:author', () => {\n it('should return books by author', async () => {\n const mockAuthor = 'John Doe';\n const mockBooks = [{ title: 'Book 1', author: mockAuthor }, { title: 'Book 2', author: mockAuthor }];\n database.getBooksByAuthor.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(\`/books/author/\${mockAuthor}\`);\n\n expect(database.getBooksByAuthor).toHaveBeenCalledWith(mockAuthor);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n\n describe('GET /books/title/:title', () => {\n it('should return books by title', async () => {\n const mockTitle = 'Book 1';\n const mockBooks = [{ title: mockTitle, author: 'John Doe' }];\n database.getBooksByTitle.mockResolvedValue(mockBooks);\n\n const response = await request(api.app).get(\`/books/title/\${mockTitle}\`);\n\n expect(database.getBooksByTitle).toHaveBeenCalledWith(mockTitle);\n expect(response.status).toBe(200);\n expect(response.body).toEqual(mockBooks);\n });\n });\n});\n`, + }] + ); + }); + test('ICommonModel#getValueInRange, issue #17424', function () { const model = worker.addModel([ diff --git a/src/vs/editor/test/common/services/languagesAssociations.test.ts b/src/vs/editor/test/common/services/languagesAssociations.test.ts index 6d0e353159e..689457fe678 100644 --- a/src/vs/editor/test/common/services/languagesAssociations.test.ts +++ b/src/vs/editor/test/common/services/languagesAssociations.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { getMimeTypes, registerPlatformLanguageAssociation, registerConfiguredLanguageAssociation } from 'vs/editor/common/services/languagesAssociations'; suite('LanguagesAssociations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Dynamically Register Text Mime', () => { let guess = getMimeTypes(URI.file('foo.monaco')); assert.deepStrictEqual(guess, ['application/unknown']); diff --git a/src/vs/editor/test/common/services/languagesRegistry.test.ts b/src/vs/editor/test/common/services/languagesRegistry.test.ts index 838be088817..74ec1559d43 100644 --- a/src/vs/editor/test/common/services/languagesRegistry.test.ts +++ b/src/vs/editor/test/common/services/languagesRegistry.test.ts @@ -5,10 +5,13 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; suite('LanguagesRegistry', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('output language does not have a name', () => { const registry = new LanguagesRegistry(false); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 07c303ba1b1..53eb701ecfb 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -11,7 +11,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextBuffer, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; import { ModelService } from 'vs/editor/common/services/modelService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -20,6 +20,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const GENERATE_TESTS = false; @@ -45,6 +46,8 @@ suite('ModelService', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('EOL setting respected depending on root', () => { const model1 = modelService.createModel('farboo', null); const model2 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\myroot\\myfile.txt' : '/myroot/myfile.txt')); @@ -53,6 +56,10 @@ suite('ModelService', () => { assert.strictEqual(model1.getOptions().defaultEOL, DefaultEndOfLine.LF); assert.strictEqual(model2.getOptions().defaultEOL, DefaultEndOfLine.CRLF); assert.strictEqual(model3.getOptions().defaultEOL, DefaultEndOfLine.LF); + + model1.dispose(); + model2.dispose(); + model3.dispose(); }); test('_computeEdits no change', function () { @@ -66,7 +73,8 @@ suite('ModelService', () => { ].join('\n') )); - const textBuffer = createTextBuffer( + const textBuffer = createAndRegisterTextBuffer( + disposables, [ 'This is line one', //16 'and this is line number two', //27 @@ -74,7 +82,7 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\n'), DefaultEndOfLine.LF - ).textBuffer; + ); const actual = ModelService._computeEdits(model, textBuffer); @@ -92,7 +100,8 @@ suite('ModelService', () => { ].join('\n') )); - const textBuffer = createTextBuffer( + const textBuffer = createAndRegisterTextBuffer( + disposables, [ 'This is line One', //16 'and this is line number two', //27 @@ -100,7 +109,7 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\n'), DefaultEndOfLine.LF - ).textBuffer; + ); const actual = ModelService._computeEdits(model, textBuffer); @@ -120,7 +129,8 @@ suite('ModelService', () => { ].join('\n') )); - const textBuffer = createTextBuffer( + const textBuffer = createAndRegisterTextBuffer( + disposables, [ 'This is line one', //16 'and this is line number two', //27 @@ -128,7 +138,7 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\r\n'), DefaultEndOfLine.LF - ).textBuffer; + ); const actual = ModelService._computeEdits(model, textBuffer); @@ -146,7 +156,8 @@ suite('ModelService', () => { ].join('\n') )); - const textBuffer = createTextBuffer( + const textBuffer = createAndRegisterTextBuffer( + disposables, [ 'This is line One', //16 'and this is line number two', //27 @@ -154,7 +165,7 @@ suite('ModelService', () => { 'and finished with the fourth.', //29 ].join('\r\n'), DefaultEndOfLine.LF - ).textBuffer; + ); const actual = ModelService._computeEdits(model, textBuffer); @@ -181,7 +192,8 @@ suite('ModelService', () => { ].join('\n') )); - const textBuffer = createTextBuffer( + const textBuffer = createAndRegisterTextBuffer( + disposables, [ 'package main', // 1 'func foo() {', // 2 @@ -189,7 +201,7 @@ suite('ModelService', () => { '' ].join('\r\n'), DefaultEndOfLine.LF - ).textBuffer; + ); const actual = ModelService._computeEdits(model, textBuffer); @@ -391,7 +403,7 @@ suite('ModelService', () => { function assertComputeEdits(lines1: string[], lines2: string[]): void { const model = createTextModel(lines1.join('\n')); - const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF).textBuffer; + const { disposable, textBuffer } = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF); // compute required edits // let start = Date.now(); @@ -402,6 +414,7 @@ function assertComputeEdits(lines1: string[], lines2: string[]): void { model.pushEditOperations([], edits, null); assert.strictEqual(model.getValue(), lines2.join('\n')); + disposable.dispose(); model.dispose(); } @@ -451,3 +464,9 @@ assertComputeEdits(file1, file2); } } } + +function createAndRegisterTextBuffer(store: DisposableStore, value: string | ITextBufferFactory | ITextSnapshot, defaultEOL: DefaultEndOfLine): ITextBuffer { + const { disposable, textBuffer } = createTextBuffer(value, defaultEOL); + store.add(disposable); + return textBuffer; +} diff --git a/src/vs/editor/test/common/services/semanticTokensDto.test.ts b/src/vs/editor/test/common/services/semanticTokensDto.test.ts index a0ffe79f823..b32e7e66c74 100644 --- a/src/vs/editor/test/common/services/semanticTokensDto.test.ts +++ b/src/vs/editor/test/common/services/semanticTokensDto.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import { IFullSemanticTokensDto, IDeltaSemanticTokensDto, encodeSemanticTokensDto, ISemanticTokensDto, decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto'; import { VSBuffer } from 'vs/base/common/buffer'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('SemanticTokensDto', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function toArr(arr: Uint32Array): number[] { const result: number[] = []; for (let i = 0, len = arr.length; i < len; i++) { diff --git a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts index bdbdde94625..83d60991849 100644 --- a/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts +++ b/src/vs/editor/test/common/services/semanticTokensProviderStyling.test.ts @@ -12,6 +12,7 @@ import { createModelServices } from 'vs/editor/test/common/testTextModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IThemeService, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ModelService', () => { let disposables: DisposableStore; @@ -28,6 +29,8 @@ suite('ModelService', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #134973: invalid semantic tokens should be handled better', () => { const languageId = 'java'; disposables.add(languageService.registerLanguage({ id: languageId })); diff --git a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts index faaf6b520ec..561d6cb23fe 100644 --- a/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts +++ b/src/vs/editor/test/common/services/textResourceConfigurationService.test.ts @@ -11,10 +11,13 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { IConfigurationValue, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { TextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('TextResourceConfigurationService - Update', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let instantiationService: TestInstantiationService; let configurationValue: IConfigurationValue = {}; let updateArgs: any[]; @@ -31,15 +34,11 @@ suite('TextResourceConfigurationService - Update', () => { let testObject: TextResourceConfigurationService; setup(() => { - instantiationService = new TestInstantiationService(); + instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(IModelService, >{ getModel() { return null; } }); instantiationService.stub(ILanguageService, >{ guessLanguageIdByFilepathOrFirstLine() { return language; } }); instantiationService.stub(IConfigurationService, configurationService); - testObject = instantiationService.createInstance(TextResourceConfigurationService); - }); - - teardown(() => { - instantiationService.dispose(); + testObject = disposables.add(instantiationService.createInstance(TextResourceConfigurationService)); }); test('updateValue writes without target and overrides when no language is defined', async () => { diff --git a/src/vs/editor/test/common/view/overviewZoneManager.test.ts b/src/vs/editor/test/common/view/overviewZoneManager.test.ts index e9b17c10147..5896b2a928b 100644 --- a/src/vs/editor/test/common/view/overviewZoneManager.test.ts +++ b/src/vs/editor/test/common/view/overviewZoneManager.test.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ColorZone, OverviewRulerZone, OverviewZoneManager } from 'vs/editor/common/viewModel/overviewZoneManager'; suite('Editor View - OverviewZoneManager', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('pixel ratio 1, dom height 600', () => { const LINE_COUNT = 50; const LINE_HEIGHT = 20; diff --git a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts index 7acf0b75f3f..ecfde1e3d91 100644 --- a/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts +++ b/src/vs/editor/test/common/viewLayout/lineDecorations.test.ts @@ -4,12 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { DecorationSegment, LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel'; suite('Editor ViewLayout - ViewLineParts', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Bug 9827:Overlapping inline decorations can cause wrong inline class to be applied', () => { const result = LineDecorationsNormalizer.normalize('abcabcabcabcabcabcabcabcabcabc', [ diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 3666fb1f888..c3f0fa635ab 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { LinesLayout, EditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { EditorWhitespace, LinesLayout } from 'vs/editor/common/viewLayout/linesLayout'; suite('Editor ViewLayout - LinesLayout', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function insertWhitespace(linesLayout: LinesLayout, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { let id: string; linesLayout.changeWhitespace((accessor) => { diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 4b8d60a1cb5..2fdbeaefdf8 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -6,10 +6,11 @@ import * as assert from 'assert'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; -import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { MetadataConsts } from 'vs/editor/common/encodedTokenAttributes'; +import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; -import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange, DomPosition, RenderLineOutput2 } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharacterMapping, DomPosition, LineRange, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { InlineDecorationType } from 'vs/editor/common/viewModel'; import { TestLineToken, TestLineTokens } from 'vs/editor/test/common/core/testLineToken'; @@ -50,6 +51,8 @@ function inflateRenderLineOutput(renderLineOutput: RenderLineOutput2) { suite('viewLineRenderer.renderLine', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void { const _actual = renderViewLine(new RenderLineInput( false, @@ -966,6 +969,8 @@ function assertCharacterMapping3(actual: CharacterMapping, expectedInfo: Charact suite('viewLineRenderer.renderLine 2', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: TestLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: LineRange[] | null) { const actual = renderViewLine(new RenderLineInput( fontIsMonospace, diff --git a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts index 9de24985297..85792a42390 100644 --- a/src/vs/editor/test/common/viewModel/lineBreakData.test.ts +++ b/src/vs/editor/test/common/viewModel/lineBreakData.test.ts @@ -4,11 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PositionAffinity } from 'vs/editor/common/model'; import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel'; import { ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData'; suite('Editor ViewModel - LineBreakData', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('Basic', () => { const data = new ModelLineProjectionData([], [], [100], [0], 10); diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index a44c69c5438..37727d4c8a1 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions'; -import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { EditorOptions, WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; -import { ModelLineProjectionData, ILineBreaksComputerFactory } from 'vs/editor/common/modelLineProjectionData'; +import { ILineBreaksComputerFactory, ModelLineProjectionData } from 'vs/editor/common/modelLineProjectionData'; +import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; function parseAnnotatedText(annotatedText: string): { text: string; indices: number[] } { let text = ''; @@ -80,6 +81,9 @@ function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number, } suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('MonospaceLineBreaksComputer', () => { const factory = new MonospaceLineBreaksComputerFactory('(', '\t).'); diff --git a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts index a0dcd9d76b1..1fdb7a2c10b 100644 --- a/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/prefixSumComputer.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { toUint32 } from 'vs/base/common/uint'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/model/prefixSumComputer'; function toUint32Array(arr: number[]): Uint32Array { @@ -18,6 +19,8 @@ function toUint32Array(arr: number[]): Uint32Array { suite('Editor ViewModel - PrefixSumComputer', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('PrefixSumComputer', () => { let indexOfResult: PrefixSumIndexOfResult; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6332e76a0b9..2a6218c8de5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3980,6 +3980,11 @@ declare namespace monaco.editor { * Defaults to true. */ sticky?: boolean; + /** + * Controls how long the hover is visible after you hovered out of it. + * Require sticky setting to be true. + */ + hidingDelay?: number; /** * Should the hover be shown above the line if possible? * Defaults to false. @@ -7343,10 +7348,6 @@ declare namespace monaco.languages { * Prefer spaces over tabs. */ insertSpaces: boolean; - /** - * The list of multiple ranges to format at once, if the provider supports it. - */ - ranges?: Range[]; } /** @@ -7621,6 +7622,13 @@ declare namespace monaco.languages { arguments?: any[]; } + export interface PendingCommentThread { + body: string; + range: IRange; + uri: Uri; + owner: string; + } + export interface CodeLens { range: IRange; id?: string; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index c6e7a926213..077393ebb51 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -39,7 +39,13 @@ export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuAct fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); } -export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean): void { +export function createAndFillInActionBarActions( + menu: IMenu, + options: IMenuActionOptions | undefined, + target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + primaryGroup?: string | ((actionGroup: string) => boolean), + shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, + useSeparatorsInPrimaryActions?: boolean): void { const groups = menu.getActions(options); const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; @@ -102,7 +108,7 @@ function fillInActions( // inlining submenus with length 0 or 1 is easy, // larger submenus need to be checked with the overall limit const submenuActions = action.actions; - if (submenuActions.length <= 1 && shouldInlineSubmenu(action, group, target.length)) { + if (shouldInlineSubmenu(action, group, target.length)) { target.splice(index, 1, ...submenuActions); } } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0b6f1148464..62feb6c2496 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -74,6 +74,7 @@ export class MenuId { static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); static readonly CommandCenter = new MenuId('CommandCenter'); + static readonly CommandCenterCenter = new MenuId('CommandCenterCenter'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); static readonly LayoutControlMenu = new MenuId('LayoutControlMenu'); static readonly MenubarMainMenu = new MenuId('MenubarMainMenu'); diff --git a/src/vs/platform/checksum/test/node/checksumService.test.ts b/src/vs/platform/checksum/test/node/checksumService.test.ts index d24d8bdea5c..3e6a29fb573 100644 --- a/src/vs/platform/checksum/test/node/checksumService.test.ts +++ b/src/vs/platform/checksum/test/node/checksumService.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ChecksumService } from 'vs/platform/checksum/node/checksumService'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -36,4 +37,6 @@ suite('Checksum Service', () => { const checksum = await checksumService.checksum(URI.file(FileAccess.asFileUri('vs/platform/checksum/test/node/fixtures/lorem.txt').fsPath)); assert.ok(checksum === '8mi5KF8kcb817zmlal1kZA' || checksum === 'DnUKbJ1bHPPNZoHgHV25sg'); // depends on line endings git config }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 5337e51d667..afa34cc695d 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -6,10 +6,10 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ConfigurationTarget, isConfigured } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; @@ -20,21 +20,20 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { Registry } from 'vs/platform/registry/common/platform'; -suite('ConfigurationService', () => { +suite('ConfigurationService.test.ts', () => { + + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let fileService: IFileService; let settingsResource: URI; - const disposables: DisposableStore = new DisposableStore(); setup(async () => { fileService = disposables.add(new FileService(new NullLogService())); const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); + disposables.add(fileService.registerProvider(Schemas.file, diskFileSystemProvider)); settingsResource = URI.file('settings.json'); }); - teardown(() => disposables.clear()); - test('simple', () => runWithFakedTimers({ useFakeTimers: true }, async () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); const testObject = disposables.add(new ConfigurationService(settingsResource, fileService, new NullPolicyService(), new NullLogService())); diff --git a/src/vs/platform/configuration/test/common/configurations.test.ts b/src/vs/platform/configuration/test/common/configurations.test.ts index 30cddfd2144..63269f63ffa 100644 --- a/src/vs/platform/configuration/test/common/configurations.test.ts +++ b/src/vs/platform/configuration/test/common/configurations.test.ts @@ -6,12 +6,14 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { equals } from 'vs/base/common/objects'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { DefaultConfiguration } from 'vs/platform/configuration/common/configurations'; import { Registry } from 'vs/platform/registry/common/platform'; suite('DefaultConfiguration', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const configurationRegistry = Registry.as(Extensions.Configuration); setup(() => reset()); @@ -24,7 +26,7 @@ suite('DefaultConfiguration', () => { } test('Test registering a property before initialize', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, @@ -43,7 +45,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering a property and do not initialize', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, @@ -61,7 +63,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering a property after initialize', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerConfiguration({ @@ -83,7 +85,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering nested properties', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, @@ -111,7 +113,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering the same property again', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, @@ -143,7 +145,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering an override identifier', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerDefaultConfigurations([{ overrides: { '[a]': { @@ -160,7 +162,7 @@ suite('DefaultConfiguration', () => { }); test('Test registering a normal property and override identifier', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, @@ -193,7 +195,7 @@ suite('DefaultConfiguration', () => { }); test('Test normal property is registered after override identifier', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerDefaultConfigurations([{ overrides: { @@ -230,7 +232,7 @@ suite('DefaultConfiguration', () => { }); test('Test override identifier is registered after property', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerConfiguration({ 'id': 'a', @@ -266,7 +268,7 @@ suite('DefaultConfiguration', () => { }); test('Test register override identifier and property after initialize', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); await testObject.initialize(); @@ -301,7 +303,7 @@ suite('DefaultConfiguration', () => { }); test('Test deregistering a property', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); const promise = Event.toPromise(testObject.onDidChangeConfiguration); const node: IConfigurationNode = { 'id': 'a', @@ -328,7 +330,7 @@ suite('DefaultConfiguration', () => { }); test('Test deregistering an override identifier', async () => { - const testObject = new DefaultConfiguration(); + const testObject = disposables.add(new DefaultConfiguration()); configurationRegistry.registerConfiguration({ 'id': 'a', 'order': 1, diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index d5b31e5516d..e7c228e98fc 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { DefaultConfiguration, PolicyConfiguration } from 'vs/platform/configuration/common/configurations'; @@ -19,14 +18,16 @@ import { deepClone } from 'vs/base/common/objects'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('PolicyConfiguration', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + let testObject: PolicyConfiguration; let fileService: IFileService; let policyService: IPolicyService; const policyFile = URI.file('policyFile').with({ scheme: 'vscode-tests' }); - const disposables = new DisposableStore(); const policyConfigurationNode: IConfigurationNode = { 'id': 'policyConfiguration', 'order': 1, @@ -64,13 +65,11 @@ suite('PolicyConfiguration', () => { await defaultConfiguration.initialize(); fileService = disposables.add(new FileService(new NullLogService())); const diskFileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(policyFile.scheme, diskFileSystemProvider); - policyService = new FilePolicyService(policyFile, fileService, new NullLogService()); + disposables.add(fileService.registerProvider(policyFile.scheme, diskFileSystemProvider)); + policyService = disposables.add(new FilePolicyService(policyFile, fileService, new NullLogService())); testObject = disposables.add(new PolicyConfiguration(defaultConfiguration, policyService, new NullLogService())); }); - teardown(() => disposables.clear()); - test('initialize: with policies', async () => { await fileService.writeFile(policyFile, VSBuffer.fromString(JSON.stringify({ 'PolicySettingA': 'policyValueA' }))); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index b63a262137f..f205cc30439 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -91,7 +91,7 @@ export interface NativeParsedArgs { 'export-default-configuration'?: string; 'install-source'?: string; 'disable-updates'?: boolean; - 'disable-keytar'?: boolean; + 'use-inmemory-secretstorage'?: boolean; 'password-store'?: string; 'disable-workspace-trust'?: boolean; 'disable-crash-reporter'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 96ccb252006..6c2e2b551ee 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -141,8 +141,8 @@ export interface INativeEnvironmentService extends IEnvironmentService { extensionsDownloadLocation: URI; builtinExtensionsPath: string; - // --- use keytar for credentials - disableKeytar?: boolean; + // --- use in-memory Secret Storage + useInMemorySecretStorage?: boolean; crossOriginIsolated?: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 3d1adfa7619..6d77e70d6b1 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -237,7 +237,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; } @memoize - get disableKeytar(): boolean { return !!this.args['disable-keytar']; } + get useInMemorySecretStorage(): boolean { return !!this.args['use-inmemory-secretstorage']; } @memoize get policyFile(): URI | undefined { diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 63243fcf3a0..c69605d16e9 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -149,7 +149,7 @@ export const OPTIONS: OptionDescriptions> = { 'skip-welcome': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, - 'disable-keytar': { type: 'boolean' }, + 'use-inmemory-secretstorage': { type: 'boolean', deprecates: ['disable-keytar'] }, 'password-store': { type: 'string' }, 'disable-workspace-trust': { type: 'boolean' }, 'disable-crash-reporter': { type: 'boolean' }, diff --git a/src/vs/platform/extensionManagement/common/extensionNls.ts b/src/vs/platform/extensionManagement/common/extensionNls.ts index 5a3e2dd582e..2f196970f3f 100644 --- a/src/vs/platform/extensionManagement/common/extensionNls.ts +++ b/src/vs/platform/extensionManagement/common/extensionNls.ts @@ -7,16 +7,17 @@ import { isObject, isString } from 'vs/base/common/types'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { localize } from 'vs/nls'; +import { ILogger } from 'vs/platform/log/common/log'; export interface ITranslations { [key: string]: string | { message: string; comment: string[] } | undefined; } -export function localizeManifest(extensionManifest: IExtensionManifest, translations: ITranslations, fallbackTranslations?: ITranslations): IExtensionManifest { +export function localizeManifest(logger: ILogger, extensionManifest: IExtensionManifest, translations: ITranslations, fallbackTranslations?: ITranslations): IExtensionManifest { try { - replaceNLStrings(extensionManifest, translations, fallbackTranslations); + replaceNLStrings(logger, extensionManifest, translations, fallbackTranslations); } catch (error) { - console.error(error?.message ?? error); + logger.error(error?.message ?? error); /*Ignore Error*/ } return extensionManifest; @@ -26,7 +27,7 @@ export function localizeManifest(extensionManifest: IExtensionManifest, translat * This routine makes the following assumptions: * The root element is an object literal */ -function replaceNLStrings(extensionManifest: IExtensionManifest, messages: ITranslations, originalMessages?: ITranslations): void { +function replaceNLStrings(logger: ILogger, extensionManifest: IExtensionManifest, messages: ITranslations, originalMessages?: ITranslations): void { const processEntry = (obj: any, key: string | number, command?: boolean) => { const value = obj[key]; if (isString(value)) { @@ -48,7 +49,7 @@ function replaceNLStrings(extensionManifest: IExtensionManifest, messages: ITran if (!message) { if (!originalMessage) { - console.warn(`[${extensionManifest.name}]: ${localize('missingNLSKey', "Couldn't find message for key {0}.", messageKey)}`); + logger.warn(`[${extensionManifest.name}]: ${localize('missingNLSKey', "Couldn't find message for key {0}.", messageKey)}`); } return; } diff --git a/src/vs/platform/extensionManagement/common/extensionTipsService.ts b/src/vs/platform/extensionManagement/common/extensionTipsService.ts index 34cea78ca9b..9c99ae68bb0 100644 --- a/src/vs/platform/extensionManagement/common/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/common/extensionTipsService.ts @@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { IConfigBasedExtensionTip, IExecutableBasedExtensionTip, IExtensionManagementService, IExtensionTipsService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; -import { disposableTimeout, timeout } from 'vs/base/common/async'; +import { disposableTimeout } from 'vs/base/common/async'; import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { join } from 'vs/base/common/path'; @@ -154,11 +154,11 @@ export abstract class AbstractNativeExtensionTipsService extends ExtensionTipsSe 3s has come out to be the good number to fetch and prompt important exe based recommendations Also fetch important exe based recommendations for reporting telemetry */ - timeout(3000).then(async () => { + this._register(disposableTimeout(async () => { await this.collectTips(); this.promptHighImportanceExeBasedTip(); this.promptMediumImportanceExeBasedTip(); - }); + }, 3000)); } override async getImportantExecutableBasedTips(): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index b25fe1cfc56..36067bcad7f 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -712,7 +712,7 @@ class ExtensionsScanner extends Disposable { return extensionManifest; } const localized = localizedMessages.values || Object.create(null); - return localizeManifest(extensionManifest, localized, defaults); + return localizeManifest(this.logService, extensionManifest, localized, defaults); } catch (error) { /*Ignore Error*/ } diff --git a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts index 7dcb41d56bf..a471a929653 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionGalleryService.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { isUUID } from 'vs/base/common/uuid'; @@ -24,6 +23,7 @@ import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/com 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'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class EnvironmentServiceMock extends mock() { override readonly serviceMachineIdResource: URI; @@ -35,7 +35,7 @@ class EnvironmentServiceMock extends mock() { } suite('Extension Gallery Service', () => { - const disposables: DisposableStore = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let fileService: IFileService, environmentService: IEnvironmentService, storageService: IStorageService, productService: IProductService, configurationService: IConfigurationService; setup(() => { @@ -43,15 +43,13 @@ suite('Extension Gallery Service', () => { environmentService = new EnvironmentServiceMock(serviceMachineIdResource); fileService = disposables.add(new FileService(new NullLogService())); const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(serviceMachineIdResource.scheme, fileSystemProvider); - storageService = new InMemoryStorageService(); + disposables.add(fileService.registerProvider(serviceMachineIdResource.scheme, fileSystemProvider)); + storageService = disposables.add(new InMemoryStorageService()); configurationService = new TestConfigurationService({ [TELEMETRY_SETTING_ID]: TelemetryConfiguration.ON }); configurationService.updateValue(TELEMETRY_SETTING_ID, TelemetryConfiguration.ON); productService = { _serviceBrand: undefined, ...product, enableTelemetry: true }; }); - teardown(() => disposables.clear()); - test('marketplace machine id', async () => { const headers = await resolveMarketplaceHeaders(product.version, productService, environmentService, configurationService, fileService, storageService, NullTelemetryService); assert.ok(isUUID(headers['X-Market-User-Id'])); diff --git a/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts b/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts index 533e23290b3..a3c2603a1eb 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionNls.test.ts @@ -5,9 +5,11 @@ import * as assert from 'assert'; import { deepClone } from 'vs/base/common/objects'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { IExtensionManifest, IConfiguration } from 'vs/platform/extensions/common/extensions'; +import { NullLogger } from 'vs/platform/log/common/log'; const manifest: IExtensionManifest = { name: 'test', @@ -44,8 +46,10 @@ const manifest: IExtensionManifest = { }; suite('Localize Manifest', () => { + const store = ensureNoDisposablesAreLeakedInTestSuite(); test('replaces template strings', function () { const localizedManifest = localizeManifest( + store.add(new NullLogger()), deepClone(manifest), { 'test.command.title': 'Test Command', @@ -63,6 +67,7 @@ suite('Localize Manifest', () => { test('replaces template strings with fallback if not found in translations', function () { const localizedManifest = localizeManifest( + store.add(new NullLogger()), deepClone(manifest), {}, { @@ -81,6 +86,7 @@ suite('Localize Manifest', () => { test('replaces template strings - command title & categories become ILocalizedString', function () { const localizedManifest = localizeManifest( + store.add(new NullLogger()), deepClone(manifest), { 'test.command.title': 'Befehl test', @@ -135,6 +141,7 @@ suite('Localize Manifest', () => { }; const localizedManifest = localizeManifest( + store.add(new NullLogger()), deepClone(manifestWithTypo), { 'test.command.title': 'Test Command', diff --git a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts index b2fa0668e72..9636eabcf69 100644 --- a/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/common/extensionsProfileScannerService.test.ts @@ -6,9 +6,9 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AbstractExtensionsProfileScannerService, ProfileExtensionsEvent } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; @@ -28,7 +28,7 @@ class TestObject extends AbstractExtensionsProfileScannerService { } suite('ExtensionsProfileScannerService', () => { const ROOT = URI.file('/ROOT'); - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const extensionsLocation = joinPath(ROOT, 'extensions'); let instantiationService: TestInstantiationService; @@ -38,22 +38,20 @@ suite('ExtensionsProfileScannerService', () => { const logService = new NullLogService(); const fileService = disposables.add(new FileService(logService)); const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(ROOT.scheme, fileSystemProvider); + disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); instantiationService.stub(ILogService, logService); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITelemetryService, NullTelemetryService); - const uriIdentityService = instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); + const uriIdentityService = instantiationService.stub(IUriIdentityService, disposables.add(new UriIdentityService(fileService))); const environmentService = instantiationService.stub(IEnvironmentService, { userRoamingDataHome: ROOT, cacheHome: joinPath(ROOT, 'cache'), }); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); instantiationService.stub(IUserDataProfilesService, userDataProfilesService); }); - teardown(() => disposables.clear()); - suiteTeardown(() => sinon.restore()); test('write extensions located in the same extensions folder', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); @@ -64,7 +62,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('write extensions located in the different folder', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(ROOT, 'pub.a-1.0.0')); @@ -75,7 +73,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('write extensions located in the same extensions folder has relative location ', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(extensionsLocation, 'pub.a-1.0.0')); @@ -86,7 +84,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('write extensions located in different extensions folder does not has relative location ', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(ROOT, 'pub.a-1.0.0')); @@ -105,7 +103,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); @@ -123,7 +121,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); @@ -141,7 +139,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extension2 = aExtension('pub.b', joinPath(extensionsLocation, 'pub.b-1.0.0')); await testObject.addExtensionsToProfile([[extension2, undefined]], extensionsManifest); @@ -173,7 +171,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension2.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ @@ -197,7 +195,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); @@ -215,7 +213,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extension2 = aExtension('pub.b', joinPath(extensionsLocation, 'pub.b-1.0.0')); await testObject.addExtensionsToProfile([[extension2, undefined]], extensionsManifest); @@ -247,7 +245,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension2.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ @@ -287,7 +285,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension4.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [ @@ -316,7 +314,7 @@ suite('ExtensionsProfileScannerService', () => { relativePath: 2 }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -333,7 +331,7 @@ suite('ExtensionsProfileScannerService', () => { relativePath: 'pub.a-1.0.0' }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -351,7 +349,7 @@ suite('ExtensionsProfileScannerService', () => { relativePath: 'pub.a-1.0.0' }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -367,7 +365,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -384,7 +382,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -400,7 +398,7 @@ suite('ExtensionsProfileScannerService', () => { location: extension.location.toJSON(), }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); try { await testObject.scanProfileExtensions(extensionsManifest); @@ -412,7 +410,7 @@ suite('ExtensionsProfileScannerService', () => { const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); await instantiationService.get(IFileService).writeFile(extensionsManifest, VSBuffer.fromString('')); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual, []); }); @@ -423,7 +421,7 @@ suite('ExtensionsProfileScannerService', () => { `)); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual, []); }); @@ -438,7 +436,7 @@ suite('ExtensionsProfileScannerService', () => { version: extension.manifest.version, }]))); - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const actual = await testObject.scanProfileExtensions(extensionsManifest); assert.deepStrictEqual(actual.map(a => ({ ...a, location: a.location.toJSON() })), [{ identifier: extension.identifier, location: extension.location.toJSON(), version: extension.manifest.version, metadata: undefined }]); @@ -448,11 +446,11 @@ suite('ExtensionsProfileScannerService', () => { }); test('add extension trigger events', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const target1 = sinon.stub(); const target2 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onDidAddExtensions(target2); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onDidAddExtensions(target2)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(ROOT, 'foo', 'pub.a-1.0.0')); @@ -477,11 +475,11 @@ suite('ExtensionsProfileScannerService', () => { }); test('remove extension trigger events', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const target1 = sinon.stub(); const target2 = sinon.stub(); - testObject.onRemoveExtensions(target1); - testObject.onDidRemoveExtensions(target2); + disposables.add(testObject.onRemoveExtensions(target1)); + disposables.add(testObject.onDidRemoveExtensions(target2)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); const extension = aExtension('pub.a', joinPath(ROOT, 'foo', 'pub.a-1.0.0')); @@ -507,7 +505,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('add extension with same id but different version', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); @@ -518,10 +516,10 @@ suite('ExtensionsProfileScannerService', () => { const target2 = sinon.stub(); const target3 = sinon.stub(); const target4 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onRemoveExtensions(target2); - testObject.onDidAddExtensions(target3); - testObject.onDidRemoveExtensions(target4); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onRemoveExtensions(target2)); + disposables.add(testObject.onDidAddExtensions(target3)); + disposables.add(testObject.onDidRemoveExtensions(target4)); const extension2 = aExtension('pub.a', joinPath(ROOT, 'pub.a-2.0.0'), undefined, { version: '2.0.0' }); await testObject.addExtensionsToProfile([[extension2, undefined]], extensionsManifest); @@ -558,7 +556,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('add same extension', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); @@ -569,10 +567,10 @@ suite('ExtensionsProfileScannerService', () => { const target2 = sinon.stub(); const target3 = sinon.stub(); const target4 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onRemoveExtensions(target2); - testObject.onDidAddExtensions(target3); - testObject.onDidRemoveExtensions(target4); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onRemoveExtensions(target2)); + disposables.add(testObject.onDidAddExtensions(target3)); + disposables.add(testObject.onDidRemoveExtensions(target4)); await testObject.addExtensionsToProfile([[extension, undefined]], extensionsManifest); const actual = await testObject.scanProfileExtensions(extensionsManifest); @@ -584,7 +582,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('add same extension with different metadata', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); @@ -595,10 +593,10 @@ suite('ExtensionsProfileScannerService', () => { const target2 = sinon.stub(); const target3 = sinon.stub(); const target4 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onRemoveExtensions(target2); - testObject.onDidAddExtensions(target3); - testObject.onDidRemoveExtensions(target4); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onRemoveExtensions(target2)); + disposables.add(testObject.onDidAddExtensions(target3)); + disposables.add(testObject.onDidRemoveExtensions(target4)); await testObject.addExtensionsToProfile([[extension, { isApplicationScoped: true }]], extensionsManifest); const actual = await testObject.scanProfileExtensions(extensionsManifest); @@ -610,7 +608,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('add extension with different version and metadata', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); @@ -622,10 +620,10 @@ suite('ExtensionsProfileScannerService', () => { const target2 = sinon.stub(); const target3 = sinon.stub(); const target4 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onRemoveExtensions(target2); - testObject.onDidAddExtensions(target3); - testObject.onDidRemoveExtensions(target4); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onRemoveExtensions(target2)); + disposables.add(testObject.onDidAddExtensions(target3)); + disposables.add(testObject.onDidRemoveExtensions(target4)); await testObject.addExtensionsToProfile([[extension2, { isApplicationScoped: true }]], extensionsManifest); const actual = await testObject.scanProfileExtensions(extensionsManifest); @@ -661,7 +659,7 @@ suite('ExtensionsProfileScannerService', () => { }); test('add extension with same id and version located in the different folder', async () => { - const testObject = instantiationService.createInstance(TestObject, extensionsLocation); + const testObject = disposables.add(instantiationService.createInstance(TestObject, extensionsLocation)); const extensionsManifest = joinPath(extensionsLocation, 'extensions.json'); @@ -672,10 +670,10 @@ suite('ExtensionsProfileScannerService', () => { const target2 = sinon.stub(); const target3 = sinon.stub(); const target4 = sinon.stub(); - testObject.onAddExtensions(target1); - testObject.onRemoveExtensions(target2); - testObject.onDidAddExtensions(target3); - testObject.onDidRemoveExtensions(target4); + disposables.add(testObject.onAddExtensions(target1)); + disposables.add(testObject.onRemoveExtensions(target2)); + disposables.add(testObject.onDidAddExtensions(target3)); + disposables.add(testObject.onDidRemoveExtensions(target4)); extension = aExtension('pub.a', joinPath(ROOT, 'pub.a-1.0.0')); await testObject.addExtensionsToProfile([[extension, undefined]], extensionsManifest); diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index 6834bb4c10f..8994ead0bf4 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { dirname, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionsProfileScannerService, IProfileExtensionsScanOptions } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { AbstractExtensionsScannerService, ExtensionScannerInput, IExtensionsScannerService, IScannedExtensionManifest, Translations } from 'vs/platform/extensionManagement/common/extensionsScannerService'; @@ -55,7 +55,7 @@ class ExtensionsScannerService extends AbstractExtensionsScannerService implemen suite('NativeExtensionsScanerService Test', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; setup(async () => { @@ -64,7 +64,7 @@ suite('NativeExtensionsScanerService Test', () => { const logService = new NullLogService(); const fileService = disposables.add(new FileService(logService)); const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(ROOT.scheme, fileSystemProvider); + disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); instantiationService.stub(ILogService, logService); instantiationService.stub(IFileService, fileService); const systemExtensionsLocation = joinPath(ROOT, 'system'); @@ -77,21 +77,19 @@ suite('NativeExtensionsScanerService Test', () => { cacheHome: joinPath(ROOT, 'cache'), }); instantiationService.stub(IProductService, { version: '1.66.0' }); - const uriIdentityService = new UriIdentityService(fileService); + const uriIdentityService = disposables.add(new UriIdentityService(fileService)); instantiationService.stub(IUriIdentityService, uriIdentityService); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); instantiationService.stub(IUserDataProfilesService, userDataProfilesService); - instantiationService.stub(IExtensionsProfileScannerService, new ExtensionsProfileScannerService(environmentService, fileService, userDataProfilesService, uriIdentityService, NullTelemetryService, logService)); + instantiationService.stub(IExtensionsProfileScannerService, disposables.add(new ExtensionsProfileScannerService(environmentService, fileService, userDataProfilesService, uriIdentityService, NullTelemetryService, logService))); await fileService.createFolder(systemExtensionsLocation); await fileService.createFolder(userExtensionsLocation); }); - teardown(() => disposables.clear()); - test('scan system extension', async () => { const manifest: Partial = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }); const extensionLocation = await aSystemExtension(manifest); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanSystemExtensions({}); @@ -110,7 +108,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan user extension', async () => { const manifest: Partial = anExtensionManifest({ 'name': 'name', 'publisher': 'pub', __metadata: { id: 'uuid' } }); const extensionLocation = await aUserExtension(manifest); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -130,7 +128,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan existing extension', async () => { const manifest: Partial = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }); const extensionLocation = await aUserExtension(manifest); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanExistingExtension(extensionLocation, ExtensionType.User, {}); @@ -149,7 +147,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan single extension', async () => { const manifest: Partial = anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }); const extensionLocation = await aUserExtension(manifest); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanOneOrMultipleExtensions(extensionLocation, ExtensionType.User, {}); @@ -168,7 +166,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan multiple extensions', async () => { const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanOneOrMultipleExtensions(dirname(extensionLocation), ExtensionType.User, {}); @@ -180,7 +178,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan user extension with different versions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' })); await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -192,7 +190,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan user extension include all versions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' })); await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2' })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({ includeAllVersions: true }); @@ -206,7 +204,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan user extension with different versions and higher version is not compatible', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' })); await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.2', engines: { vscode: '^1.67.0' } })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -218,7 +216,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan exclude invalid extensions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -230,7 +228,7 @@ suite('NativeExtensionsScanerService Test', () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -242,7 +240,7 @@ suite('NativeExtensionsScanerService Test', () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub' })); await instantiationService.get(IFileService).writeFile(joinPath(URI.file(instantiationService.get(INativeEnvironmentService).extensionsPath), '.obsolete'), VSBuffer.fromString(JSON.stringify({ 'pub.name2-1.0.0': true }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({ includeUninstalled: true }); @@ -254,7 +252,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan include invalid extensions', async () => { await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' })); await aUserExtension(anExtensionManifest({ 'name': 'name2', 'publisher': 'pub', engines: { vscode: '^1.67.0' } })); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({ includeInvalid: true }); @@ -275,7 +273,7 @@ suite('NativeExtensionsScanerService Test', () => { const extensionLocation = await anExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub' }), joinPath(ROOT, 'additional')); await aSystemExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', version: '1.0.1' })); await instantiationService.get(IFileService).writeFile(joinPath(instantiationService.get(INativeEnvironmentService).userHome, '.vscode-oss-dev', 'extensions', 'control.json'), VSBuffer.fromString(JSON.stringify({ 'pub.name2': 'disabled', 'pub.name': extensionLocation.fsPath }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanSystemExtensions({ checkControlFile: true }); @@ -287,7 +285,7 @@ suite('NativeExtensionsScanerService Test', () => { test('scan extension with default nls replacements', async () => { const extensionLocation = await aUserExtension(anExtensionManifest({ 'name': 'name', 'publisher': 'pub', displayName: '%displayName%' })); await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); const actual = await testObject.scanUserExtensions({}); @@ -301,7 +299,7 @@ suite('NativeExtensionsScanerService Test', () => { await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' }))); const nlsLocation = joinPath(extensionLocation, 'package.en.json'); await instantiationService.get(IFileService).writeFile(nlsLocation, VSBuffer.fromString(JSON.stringify({ contents: { package: { displayName: 'Hello World EN' } } }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); translations = { 'pub.name': nlsLocation.fsPath }; const actual = await testObject.scanUserExtensions({ language: 'en' }); @@ -316,7 +314,7 @@ suite('NativeExtensionsScanerService Test', () => { await instantiationService.get(IFileService).writeFile(joinPath(extensionLocation, 'package.nls.json'), VSBuffer.fromString(JSON.stringify({ displayName: 'Hello World' }))); const nlsLocation = joinPath(extensionLocation, 'package.en.json'); await instantiationService.get(IFileService).writeFile(nlsLocation, VSBuffer.fromString(JSON.stringify({ contents: { package: { displayName: 'Hello World EN' } } }))); - const testObject: IExtensionsScannerService = instantiationService.createInstance(ExtensionsScannerService); + const testObject: IExtensionsScannerService = disposables.add(instantiationService.createInstance(ExtensionsScannerService)); translations = { 'pub.name2': nlsLocation.fsPath }; const actual = await testObject.scanUserExtensions({ language: 'en' }); diff --git a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts index a4ca27c91fc..51395a86737 100644 --- a/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts +++ b/src/vs/platform/extensionManagement/test/node/installGalleryExtensionTask.test.ts @@ -14,6 +14,7 @@ import { isBoolean } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -74,9 +75,9 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask { ) { const instantiationService = disposables.add(new TestInstantiationService()); const logService = instantiationService.stub(ILogService, new NullLogService()); - const fileService = instantiationService.stub(IFileService, new FileService(logService)); - const fileSystemProvider = new InMemoryFileSystemProvider(); - fileService.registerProvider(ROOT.scheme, fileSystemProvider); + const fileService = instantiationService.stub(IFileService, disposables.add(new FileService(logService))); + const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); const systemExtensionsLocation = joinPath(ROOT, 'system'); const userExtensionsLocation = joinPath(ROOT, 'extensions'); instantiationService.stub(INativeEnvironmentService, { @@ -89,10 +90,10 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask { }); instantiationService.stub(IProductService, {}); instantiationService.stub(ITelemetryService, NullTelemetryService); - const uriIdentityService = instantiationService.stub(IUriIdentityService, instantiationService.createInstance(UriIdentityService)); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, instantiationService.createInstance(UserDataProfilesService)); - const extensionsProfileScannerService = instantiationService.stub(IExtensionsProfileScannerService, instantiationService.createInstance(ExtensionsProfileScannerService)); - const extensionsScannerService = instantiationService.stub(IExtensionsScannerService, instantiationService.createInstance(ExtensionsScannerService)); + const uriIdentityService = instantiationService.stub(IUriIdentityService, disposables.add(instantiationService.createInstance(UriIdentityService))); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, disposables.add(instantiationService.createInstance(UserDataProfilesService))); + const extensionsProfileScannerService = instantiationService.stub(IExtensionsProfileScannerService, disposables.add(instantiationService.createInstance(ExtensionsProfileScannerService))); + const extensionsScannerService = instantiationService.stub(IExtensionsScannerService, disposables.add(instantiationService.createInstance(ExtensionsScannerService))); super( { name: extension.name, @@ -127,12 +128,10 @@ class TestInstallGalleryExtensionTask extends InstallGalleryExtensionTask { suite('InstallGalleryExtensionTask Tests', () => { - const disposables = new DisposableStore(); - - teardown(() => disposables.clear()); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); test('if verification is enabled by default, the task completes', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true }), disposables.add(new DisposableStore())); await testObject.run(); @@ -141,7 +140,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('if verification is enabled in stable, the task completes', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true, quality: 'stable' }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true, quality: 'stable' }), disposables.add(new DisposableStore())); await testObject.run(); @@ -150,7 +149,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('if verification is disabled by setting set to false, the task skips verification', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: false, verificationResult: 'error', didExecute: false }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: false, verificationResult: 'error', didExecute: false }), disposables.add(new DisposableStore())); await testObject.run(); @@ -159,7 +158,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('if verification is disabled because the module is not loaded, the task skips verification', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: false, didExecute: false }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: false, didExecute: false }), disposables.add(new DisposableStore())); await testObject.run(); @@ -169,7 +168,7 @@ suite('InstallGalleryExtensionTask Tests', () => { test('if verification fails to execute, the task completes', async () => { const errorCode = 'ENOENT'; - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: errorCode, didExecute: false }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: errorCode, didExecute: false }), disposables.add(new DisposableStore())); await testObject.run(); @@ -180,7 +179,7 @@ suite('InstallGalleryExtensionTask Tests', () => { test('if verification fails', async () => { const errorCode = 'IntegrityCheckFailed'; - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: errorCode, didExecute: true }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: errorCode, didExecute: true }), disposables.add(new DisposableStore())); await testObject.run(); @@ -189,7 +188,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('if verification succeeds, the task completes', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: true }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: true }), disposables.add(new DisposableStore())); await testObject.run(); @@ -198,7 +197,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('task completes for unsigned extension', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: false }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: false }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: false }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: true, didExecute: false }), disposables.add(new DisposableStore())); await testObject.run(); @@ -207,7 +206,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); test('task completes for an unsigned extension even when signature verification throws error', async () => { - const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: false }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: 'error', didExecute: true }), disposables); + const testObject = new TestInstallGalleryExtensionTask(aGalleryExtension('a', { isSigned: false }), anExtensionsDownloader({ isSignatureVerificationEnabled: true, verificationResult: 'error', didExecute: true }), disposables.add(new DisposableStore())); await testObject.run(); @@ -219,9 +218,9 @@ suite('InstallGalleryExtensionTask Tests', () => { const logService = new NullLogService(); const fileService = disposables.add(new FileService(logService)); const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(ROOT.scheme, fileSystemProvider); + disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); - const instantiationService = new TestInstantiationService(); + const instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(IProductService, { quality: options.quality ?? 'insiders' }); instantiationService.stub(IFileService, fileService); instantiationService.stub(ILogService, logService); @@ -236,7 +235,7 @@ suite('InstallGalleryExtensionTask Tests', () => { }); instantiationService.stub(IConfigurationService, new TestConfigurationService(isBoolean(options.isSignatureVerificationEnabled) ? { extensions: { verifySignature: options.isSignatureVerificationEnabled } } : undefined)); instantiationService.stub(IExtensionSignatureVerificationService, new TestExtensionSignatureVerificationService(options.verificationResult, !!options.didExecute)); - return instantiationService.createInstance(ExtensionsDownloader); + return disposables.add(instantiationService.createInstance(ExtensionsDownloader)); } function aGalleryExtension(name: string, properties: Partial = {}, galleryExtensionProperties: any = {}, assets: Partial = {}): IGalleryExtension { diff --git a/src/vs/platform/files/common/diskFileSystemProviderClient.ts b/src/vs/platform/files/common/diskFileSystemProviderClient.ts index f277238bbd3..d3719ddd0e7 100644 --- a/src/vs/platform/files/common/diskFileSystemProviderClient.ts +++ b/src/vs/platform/files/common/diskFileSystemProviderClient.ts @@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents } from 'vs/base/common/stream'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -93,9 +93,10 @@ export class DiskFileSystemProviderClient extends Disposable implements readFileStream(resource: URI, opts: IFileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + const disposables = new DisposableStore(); // Reading as file stream goes through an event to the remote side - const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { + disposables.add(this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { // data if (dataOrErrorOrEnd instanceof VSBuffer) { @@ -128,12 +129,12 @@ export class DiskFileSystemProviderClient extends Disposable implements } // Signal to the remote side that we no longer listen - listener.dispose(); + disposables.dispose(); } - }); + })); // Support cancellation - token.onCancellationRequested(() => { + disposables.add(token.onCancellationRequested(() => { // Ensure to end the stream properly with an error // to indicate the cancellation. @@ -143,8 +144,8 @@ export class DiskFileSystemProviderClient extends Disposable implements // Ensure to dispose the listener upon cancellation. This will // bubble through the remote side as event and allows to stop // reading the file. - listener.dispose(); - }); + disposables.dispose(); + })); return stream; } diff --git a/src/vs/platform/files/node/watcher/watcherMain.ts b/src/vs/platform/files/node/watcher/watcherMain.ts index e3fd4ca9cde..09f952c4f4a 100644 --- a/src/vs/platform/files/node/watcher/watcherMain.ts +++ b/src/vs/platform/files/node/watcher/watcherMain.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server as ChildProcessServer } from 'vs/base/parts/ipc/node/ipc.cp'; import { Server as UtilityProcessServer } from 'vs/base/parts/ipc/node/ipc.mp'; @@ -17,4 +18,4 @@ if (isUtilityProcess(process)) { } const service = new UniversalWatcher(); -server.registerChannel('watcher', ProxyChannel.fromService(service)); +server.registerChannel('watcher', ProxyChannel.fromService(service, new DisposableStore())); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 6b698bc17f6..75b13f0a8fc 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -1469,7 +1469,7 @@ configurationRegistry.registerConfiguration({ type: 'string', enum: ['automatic', 'trigger'], default: 'automatic', - description: localize('typeNavigationMode', "Controls the how type navigation works in lists and trees in the workbench. When set to 'trigger', type navigation begins once the 'list.triggerTypeNavigation' command is run."), + markdownDescription: localize('typeNavigationMode2', "Controls how type navigation works in lists and trees in the workbench. When set to `trigger`, type navigation begins once the `list.triggerTypeNavigation` command is run."), } } }); diff --git a/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts b/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts index a4a7a964aa8..a5bacbb8c86 100644 --- a/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts +++ b/src/vs/platform/remote/test/electron-sandbox/remoteAuthorityResolverService.test.ts @@ -4,12 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; suite('RemoteAuthorityResolverService', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #147318: RemoteAuthorityResolverError keeps the same type', async () => { const productService: IProductService = { _serviceBrand: undefined, ...product }; const service = new RemoteAuthorityResolverService(productService, undefined as any); @@ -21,5 +25,6 @@ suite('RemoteAuthorityResolverService', () => { } catch (err) { assert.strictEqual(RemoteAuthorityResolverError.isTemporarilyNotAvailable(err), true); } + service.dispose(); }); }); diff --git a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts index 08ba634bad0..925cf35d2a3 100644 --- a/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts +++ b/src/vs/platform/remoteTunnel/node/remoteTunnelService.ts @@ -21,6 +21,7 @@ import { hostname, homedir } from 'os'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { isString } from 'vs/base/common/types'; import { StreamSplitter } from 'vs/base/node/nodeStreams'; +import { joinPath } from 'vs/base/common/resources'; type RemoteTunnelEnablementClassification = { owner: 'aeschli'; @@ -93,7 +94,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ @IStorageService private readonly storageService: IStorageService ) { super(); - this._logger = this._register(loggerService.createLogger(LOG_ID, { name: LOGGER_NAME })); + this._logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${LOG_ID}.log`), { id: LOG_ID, name: LOGGER_NAME })); this._startTunnelProcessDelayer = new Delayer(100); this._register(this._logger.onDidChangeLogLevel(l => this._logger.info('Log level changed to ' + LogLevelToString(l)))); diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 125b79f2556..6dfcb237289 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -85,7 +85,7 @@ export function isSuccess(context: IRequestContext): boolean { return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223; } -function hasNoContent(context: IRequestContext): boolean { +export function hasNoContent(context: IRequestContext): boolean { return context.res.statusCode === 204; } diff --git a/src/vs/platform/state/test/node/state.test.ts b/src/vs/platform/state/test/node/state.test.ts index c77c3a86e3f..493d78d0e51 100644 --- a/src/vs/platform/state/test/node/state.test.ts +++ b/src/vs/platform/state/test/node/state.test.ts @@ -170,6 +170,8 @@ flakySuite('StateService', () => { assert.strictEqual(service.getItem('some.setItems.key3'), undefined); assert.strictEqual(service.getItem('some.setItems.key4'), undefined); assert.strictEqual(service.getItem('some.setItems.key5'), undefined); + + return service.close(); }); test('Multiple ops are buffered and applied', async function () { @@ -199,6 +201,8 @@ flakySuite('StateService', () => { assert.strictEqual(service.getItem('some.key2'), 'some.value2'); assert.strictEqual(service.getItem('some.key3'), 'some.value3'); assert.strictEqual(service.getItem('some.key4'), undefined); + + return service.close(); }); test('Multiple ops (Immediate Strategy)', async function () { @@ -228,6 +232,8 @@ flakySuite('StateService', () => { assert.strictEqual(service.getItem('some.key2'), 'some.value2'); assert.strictEqual(service.getItem('some.key3'), 'some.value3'); assert.strictEqual(service.getItem('some.key4'), undefined); + + return service.close(); }); test('Used before init', async function () { @@ -253,6 +259,8 @@ flakySuite('StateService', () => { assert.strictEqual(service.getItem('some.key2'), 'some.value2'); assert.strictEqual(service.getItem('some.key3'), 'some.value3'); assert.strictEqual(service.getItem('some.key4'), undefined); + + return service.close(); }); test('Used after close', async function () { @@ -276,7 +284,7 @@ flakySuite('StateService', () => { assert.ok(contents.includes('some.value1')); assert.ok(!contents.includes('some.marker')); - await service.close(); + return service.close(); }); test('Closed before init', async function () { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 2af9ffd0281..174a3038aaa 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -748,6 +748,10 @@ export class InMemoryStorageService extends AbstractStorageService { // no-op when in-memory } + protected override shouldFlushWhenIdle(): boolean { + return false; + } + hasScope(scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean { return false; } diff --git a/src/vs/platform/terminal/common/terminalLogService.ts b/src/vs/platform/terminal/common/terminalLogService.ts index 3e1e1a5ca69..fe5e621ff89 100644 --- a/src/vs/platform/terminal/common/terminalLogService.ts +++ b/src/vs/platform/terminal/common/terminalLogService.ts @@ -9,6 +9,8 @@ import { localize } from 'vs/nls'; import { ILogger, ILoggerService, LogLevel } from 'vs/platform/log/common/log'; import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { joinPath } from 'vs/base/common/resources'; export class TerminalLogService extends Disposable implements ITerminalLogService { declare _serviceBrand: undefined; @@ -22,10 +24,11 @@ export class TerminalLogService extends Disposable implements ITerminalLogServic constructor( @ILoggerService private readonly _loggerService: ILoggerService, - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService + @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); - this._logger = this._loggerService.createLogger('terminal', { name: localize('terminalLoggerName', 'Terminal') }); + this._logger = this._loggerService.createLogger(joinPath(environmentService.logsHome, 'terminal.log'), { id: 'terminal', name: localize('terminalLoggerName', 'Terminal') }); this._register(Event.runAndSubscribe(workspaceContextService.onDidChangeWorkspaceFolders, () => { this._workspaceId = workspaceContextService.getWorkspace().id.substring(0, 7); })); diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index 01ab403711e..e901ffd34f8 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -353,7 +353,7 @@ const terminalPlatformConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.IgnoreProcessNames]: { - description: localize('terminal.integrated.confirmIgnoreProcesses', "A set of process names to ignore when using the {0} setting.", '`terminal.integrated.confirmOnKill`'), + markdownDescription: localize('terminal.integrated.confirmIgnoreProcesses', "A set of process names to ignore when using the {0} setting.", '`#terminal.integrated.confirmOnKill#`'), type: 'array', items: { type: 'string', diff --git a/src/vs/platform/terminal/node/childProcessMonitor.ts b/src/vs/platform/terminal/node/childProcessMonitor.ts index 8e819de519d..9586353a96a 100644 --- a/src/vs/platform/terminal/node/childProcessMonitor.ts +++ b/src/vs/platform/terminal/node/childProcessMonitor.ts @@ -29,8 +29,6 @@ export const ignoreProcessNames: string[] = []; * calls into the monitor. */ export class ChildProcessMonitor extends Disposable { - private _isDisposed: boolean = false; - private _hasChildProcesses: boolean = false; private set hasChildProcesses(value: boolean) { if (this._hasChildProcesses !== value) { @@ -57,11 +55,6 @@ export class ChildProcessMonitor extends Disposable { super(); } - override dispose() { - this._isDisposed = true; - super.dispose(); - } - /** * Input was triggered on the process. */ @@ -78,7 +71,7 @@ export class ChildProcessMonitor extends Disposable { @debounce(Constants.ActiveDebounceDuration) private async _refreshActive(): Promise { - if (this._isDisposed) { + if (this._store.isDisposed) { return; } try { diff --git a/src/vs/platform/terminal/node/ptyHostMain.ts b/src/vs/platform/terminal/node/ptyHostMain.ts index cd0faa5ff20..f3693acbda3 100644 --- a/src/vs/platform/terminal/node/ptyHostMain.ts +++ b/src/vs/platform/terminal/node/ptyHostMain.ts @@ -21,6 +21,7 @@ import { HeartbeatService } from 'vs/platform/terminal/node/heartbeatService'; import { PtyService } from 'vs/platform/terminal/node/ptyService'; import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes'; import { timeout } from 'vs/base/common/async'; +import { DisposableStore } from 'vs/base/common/lifecycle'; startPtyHost(); @@ -72,13 +73,15 @@ async function startPtyHost() { logService.warn(`Pty host is simulating ${simulatedLatency}ms latency`); } + const disposables = new DisposableStore(); + // Heartbeat responsiveness tracking const heartbeatService = new HeartbeatService(); - server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService)); + server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService, disposables)); // Init pty service const ptyService = new PtyService(logService, productService, reconnectConstants, simulatedLatency); - const ptyServiceChannel = ProxyChannel.fromService(ptyService); + const ptyServiceChannel = ProxyChannel.fromService(ptyService, disposables); server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel); // Register a channel for direct communication via Message Port diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 324e4063d95..f91b87baed4 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -63,7 +63,6 @@ export class PtyHostService extends Disposable implements IPtyHostService { private _wasQuitRequested = false; private _restartCount = 0; private _isResponsive = true; - private _isDisposed = false; private _heartbeatFirstTimeout?: NodeJS.Timeout; private _heartbeatSecondTimeout?: NodeJS.Timeout; @@ -158,7 +157,7 @@ export class PtyHostService extends Disposable implements IPtyHostService { // Handle exit this._register(connection.onDidProcessExit(e => { this._onPtyHostExit.fire(e.code); - if (!this._wasQuitRequested && !this._isDisposed) { + if (!this._wasQuitRequested && !this._store.isDisposed) { if (this._restartCount <= Constants.MaxRestarts) { this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}`); this._restartCount++; @@ -196,11 +195,6 @@ export class PtyHostService extends Disposable implements IPtyHostService { return [connection, proxy]; } - override dispose() { - this._isDisposed = true; - super.dispose(); - } - async createProcess( shellLaunchConfig: IShellLaunchConfig, cwd: string, diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index b237e821fcc..a9ef29ca05e 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -6,7 +6,7 @@ import { exec } from 'child_process'; import { timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import * as path from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -106,7 +106,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private _ptyProcess: IPty | undefined; private _currentTitle: string = ''; private _processStartupComplete: Promise | undefined; - private _isDisposed: boolean = false; private _windowsShellHelper: WindowsShellHelper | undefined; private _childProcessMonitor: ChildProcessMonitor | undefined; private _titleInterval: NodeJS.Timer | null = null; @@ -190,6 +189,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._register(this._windowsShellHelper.onShellNameChanged(e => this._onDidChangeProperty.fire({ type: ProcessPropertyType.Title, value: e }))); }); } + this._register(toDisposable(() => { + if (this._titleInterval) { + clearInterval(this._titleInterval); + this._titleInterval = null; + } + })); } async start(): Promise { @@ -327,15 +332,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess this._setupTitlePolling(ptyProcess); } - override dispose(): void { - this._isDisposed = true; - if (this._titleInterval) { - clearInterval(this._titleInterval); - } - this._titleInterval = null; - super.dispose(); - } - private _setupTitlePolling(ptyProcess: IPty) { // Send initial timeout async to give event listeners a chance to init setTimeout(() => this._sendProcessTitle(ptyProcess)); @@ -368,7 +364,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess // Wait to kill to process until the start up code has run. This prevents us from firing a process exit before a // process start. await this._processStartupComplete; - if (this._isDisposed) { + if (this._store.isDisposed) { return; } // Attempt to kill the pty, it may have already been killed at this @@ -408,7 +404,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } private _sendProcessTitle(ptyProcess: IPty): void { - if (this._isDisposed) { + if (this._store.isDisposed) { return; } this._currentTitle = ptyProcess.process; @@ -428,11 +424,11 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (immediate && !isWindows) { this._kill(); } else { - if (!this._closeTimeout && !this._isDisposed) { + if (!this._closeTimeout && !this._store.isDisposed) { this._queueProcessExit(); // Allow a maximum amount of time for the process to exit, otherwise force kill it setTimeout(() => { - if (this._closeTimeout && !this._isDisposed) { + if (this._closeTimeout && !this._store.isDisposed) { this._closeTimeout = undefined; this._kill(); } @@ -442,7 +438,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } input(data: string, isBinary?: boolean): void { - if (this._isDisposed || !this._ptyProcess) { + if (this._store.isDisposed || !this._ptyProcess) { return; } for (let i = 0; i <= Math.floor(data.length / Constants.WriteMaxChunkSize); i++) { @@ -523,7 +519,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } resize(cols: number, rows: number): void { - if (this._isDisposed) { + if (this._store.isDisposed) { return; } if (typeof cols !== 'number' || typeof rows !== 'number' || isNaN(cols) || isNaN(rows)) { @@ -650,15 +646,6 @@ class DelayedResizer extends Disposable { this._timeout = setTimeout(() => { this._onTrigger.fire({ rows: this.rows, cols: this.cols }); }, 1000); - this._register({ - dispose: () => { - clearTimeout(this._timeout); - } - }); - } - - override dispose(): void { - super.dispose(); - clearTimeout(this._timeout); + this._register(toDisposable(() => clearTimeout(this._timeout))); } } diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index b93e17247a2..1def6545d69 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -35,7 +35,6 @@ const SHELL_EXECUTABLES = [ let windowsProcessTree: typeof WindowsProcessTreeType; export class WindowsShellHelper extends Disposable implements IWindowsShellHelper { - private _isDisposed: boolean; private _currentRequest: Promise | undefined; private _shellType: TerminalShellType | undefined; get shellType(): TerminalShellType | undefined { return this._shellType; } @@ -55,13 +54,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe throw new Error(`WindowsShellHelper cannot be instantiated on ${platform}`); } - this._isDisposed = false; - this._startMonitoringShell(); } private async _startMonitoringShell(): Promise { - if (this._isDisposed) { + if (this._store.isDisposed) { return; } this.checkShell(); @@ -112,16 +109,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe return this.traverseTree(tree.children[favouriteChild]); } - override dispose(): void { - this._isDisposed = true; - super.dispose(); - } - /** * Returns the innermost shell executable running in the terminal */ async getShellName(): Promise { - if (this._isDisposed) { + if (this._store.isDisposed) { return Promise.resolve(''); } // Prevent multiple requests at once, instead return current request diff --git a/src/vs/platform/theme/browser/iconsStyleSheet.ts b/src/vs/platform/theme/browser/iconsStyleSheet.ts index fc788a234a7..6bfce5b466d 100644 --- a/src/vs/platform/theme/browser/iconsStyleSheet.ts +++ b/src/vs/platform/theme/browser/iconsStyleSheet.ts @@ -5,22 +5,28 @@ import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { getIconRegistry, IconContribution, IconFontDefinition } from 'vs/platform/theme/common/iconRegistry'; import { IProductIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -export interface IIconsStyleSheet { +export interface IIconsStyleSheet extends IDisposable { getCSS(): string; readonly onDidChange: Event; } export function getIconsStyleSheet(themeService: IThemeService | undefined): IIconsStyleSheet { - const onDidChangeEmmiter = new Emitter(); + const disposable = new DisposableStore(); + + const onDidChangeEmmiter = disposable.add(new Emitter()); const iconRegistry = getIconRegistry(); - iconRegistry.onDidChange(() => onDidChangeEmmiter.fire()); - themeService?.onDidProductIconThemeChange(() => onDidChangeEmmiter.fire()); + disposable.add(iconRegistry.onDidChange(() => onDidChangeEmmiter.fire())); + if (themeService) { + disposable.add(themeService.onDidProductIconThemeChange(() => onDidChangeEmmiter.fire())); + } return { + dispose: () => disposable.dispose(), onDidChange: onDidChangeEmmiter.event, getCSS() { const productIconTheme = themeService ? themeService.getProductIconTheme() : new UnthemedProductIconTheme(); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index adc559c304e..9f66d2cd1a5 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -395,7 +395,7 @@ export const editorInlayHintParameterForeground = registerColor('editorInlayHint export const editorInlayHintParameterBackground = registerColor('editorInlayHint.parameterBackground', { dark: editorInlayHintBackground, light: editorInlayHintBackground, hcDark: editorInlayHintBackground, hcLight: editorInlayHintBackground }, nls.localize('editorInlayHintBackgroundParameter', 'Background color of inline hints for parameters')); /** - * Editor lighbulb icon colors + * Editor lightbulb icon colors */ export const editorLightBulbForeground = registerColor('editorLightBulb.foreground', { dark: '#FFCC00', light: '#DDB100', hcDark: '#FFCC00', hcLight: '#007ACC' }, nls.localize('editorLightBulbForeground', "The color used for the lightbulb actions icon.")); export const editorLightBulbAutoFixForeground = registerColor('editorLightBulbAutoFix.foreground', { dark: '#75BEFF', light: '#007ACC', hcDark: '#75BEFF', hcLight: '#007ACC' }, nls.localize('editorLightBulbAutoFixForeground', "The color used for the lightbulb auto fix actions icon.")); @@ -547,8 +547,9 @@ export const overviewRulerSelectionHighlightForeground = registerColor('editorOv export const minimapFindMatch = registerColor('minimap.findMatchHighlight', { light: '#d18616', dark: '#d18616', hcDark: '#AB5A00', hcLight: '#0F4A85' }, nls.localize('minimapFindMatchHighlight', 'Minimap marker color for find matches.'), true); export const minimapSelectionOccurrenceHighlight = registerColor('minimap.selectionOccurrenceHighlight', { light: '#c9c9c9', dark: '#676767', hcDark: '#ffffff', hcLight: '#0F4A85' }, nls.localize('minimapSelectionOccurrenceHighlight', 'Minimap marker color for repeating editor selections.'), true); export const minimapSelection = registerColor('minimap.selectionHighlight', { light: '#ADD6FF', dark: '#264F78', hcDark: '#ffffff', hcLight: '#0F4A85' }, nls.localize('minimapSelectionHighlight', 'Minimap marker color for the editor selection.'), true); -export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hcDark: new Color(new RGBA(255, 50, 50, 1)), hcLight: '#B5200D' }, nls.localize('minimapError', 'Minimap marker color for errors.')); +export const minimapInfo = registerColor('minimap.infoHighlight', { dark: editorInfoForeground, light: editorInfoForeground, hcDark: editorInfoBorder, hcLight: editorInfoBorder }, nls.localize('minimapInfo', 'Minimap marker color for infos.')); export const minimapWarning = registerColor('minimap.warningHighlight', { dark: editorWarningForeground, light: editorWarningForeground, hcDark: editorWarningBorder, hcLight: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Minimap marker color for warnings.')); +export const minimapError = registerColor('minimap.errorHighlight', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hcDark: new Color(new RGBA(255, 50, 50, 1)), hcLight: '#B5200D' }, nls.localize('minimapError', 'Minimap marker color for errors.')); export const minimapBackground = registerColor('minimap.background', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('minimapBackground', "Minimap background color.")); export const minimapForegroundOpacity = registerColor('minimap.foregroundOpacity', { dark: Color.fromHex('#000f'), light: Color.fromHex('#000f'), hcDark: Color.fromHex('#000f'), hcLight: Color.fromHex('#000f') }, nls.localize('minimapForegroundOpacity', 'Opacity of foreground elements rendered in the minimap. For example, "#000000c0" will render the elements with 75% opacity.')); diff --git a/src/vs/platform/userData/common/fileUserDataProvider.ts b/src/vs/platform/userData/common/fileUserDataProvider.ts index 9382a676acb..47239b78561 100644 --- a/src/vs/platform/userData/common/fileUserDataProvider.ts +++ b/src/vs/platform/userData/common/fileUserDataProvider.ts @@ -12,6 +12,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { VSBuffer } from 'vs/base/common/buffer'; import { isObject } from 'vs/base/common/types'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ResourceSet } from 'vs/base/common/map'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; /** * This is a wrapper on top of the local filesystem provider which will @@ -31,17 +34,33 @@ export class FileUserDataProvider extends Disposable implements readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly watchResources = TernarySearchTree.forUris(() => !(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); + private readonly atomicWritesResources: ResourceSet; constructor( private readonly fileSystemScheme: string, private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability & (IFileSystemProviderWithFileReadStreamCapability | IFileSystemProviderWithFileAtomicReadCapability | IFileSystemProviderWithFileFolderCopyCapability), private readonly userDataScheme: string, + private readonly userDataProfilesService: IUserDataProfilesService, + uriIdentityService: IUriIdentityService, private readonly logService: ILogService, ) { super(); + this.atomicWritesResources = new ResourceSet((uri) => uriIdentityService.extUri.getComparisonKey(this.toFileSystemResource(uri))); + this.updateAtomicWritesResources(); + this._register(userDataProfilesService.onDidChangeProfiles(() => this.updateAtomicWritesResources())); this._register(this.fileSystemProvider.onDidChangeFile(e => this.handleFileChanges(e))); } + private updateAtomicWritesResources(): void { + this.atomicWritesResources.clear(); + for (const profile of this.userDataProfilesService.profiles) { + this.atomicWritesResources.add(profile.settingsResource); + this.atomicWritesResources.add(profile.keybindingsResource); + this.atomicWritesResources.add(profile.tasksResource); + this.atomicWritesResources.add(profile.extensionsResource); + } + } + watch(resource: URI, opts: IWatchOptions): IDisposable { this.watchResources.set(resource, resource); const disposable = this.fileSystemProvider.watch(this.toFileSystemResource(resource), opts); @@ -86,7 +105,7 @@ export class FileUserDataProvider extends Disposable implements } writeFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { - if (!isObject(opts.atomic) && hasFileAtomicWriteCapability(this.fileSystemProvider)) { + if (this.atomicWritesResources.has(resource) && !isObject(opts.atomic) && hasFileAtomicWriteCapability(this.fileSystemProvider)) { opts = { ...opts, atomic: { postfix: '.vsctmp' } }; } return this.fileSystemProvider.writeFile(this.toFileSystemResource(resource), content, opts); diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index be1d5377a39..2a88a1d2c5f 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -20,6 +20,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import product from 'vs/platform/product/common/product'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -38,7 +39,7 @@ suite('FileUserDataProvider', () => { let backupWorkspaceHomeOnDisk: URI; let environmentService: IEnvironmentService; let userDataProfilesService: IUserDataProfilesService; - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let fileUserDataProvider: FileUserDataProvider; setup(async () => { @@ -54,15 +55,14 @@ suite('FileUserDataProvider', () => { await testObject.createFolder(backupWorkspaceHomeOnDisk); environmentService = new TestEnvironmentService(userDataHomeOnDisk); - userDataProfilesService = new UserDataProfilesService(environmentService, testObject, new UriIdentityService(testObject), logService); + const uriIdentityService = disposables.add(new UriIdentityService(testObject)); + userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, testObject, uriIdentityService, logService)); - fileUserDataProvider = new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService); + fileUserDataProvider = disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService)); disposables.add(fileUserDataProvider); disposables.add(testObject.registerProvider(Schemas.vscodeUserData, fileUserDataProvider)); }); - teardown(() => disposables.clear()); - test('exists return false when file does not exist', async () => { const exists = await testObject.exists(userDataProfilesService.defaultProfile.settingsResource); assert.strictEqual(exists, false); @@ -314,8 +314,14 @@ suite('FileUserDataProvider - Watching', () => { let fileEventEmitter: Emitter; setup(() => { + const logService = new NullLogService(); + const fileService = disposables.add(new FileService(logService)); + const environmentService = new TestEnvironmentService(rootFileResource); + const uriIdentityService = disposables.add(new UriIdentityService(fileService)); + const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); + fileEventEmitter = disposables.add(new Emitter()); - testObject = disposables.add(new FileUserDataProvider(rootFileResource.scheme, new TestFileSystemProvider(fileEventEmitter.event), Schemas.vscodeUserData, new NullLogService())); + testObject = disposables.add(new FileUserDataProvider(rootFileResource.scheme, new TestFileSystemProvider(fileEventEmitter.event), Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService())); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index 38f24155224..7e898dd15fa 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -9,12 +9,12 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { InMemoryUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -28,7 +28,7 @@ class TestEnvironmentService extends AbstractNativeEnvironmentService { suite('UserDataProfileService (Common)', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let testObject: UserDataProfilesService; let environmentService: TestEnvironmentService; @@ -40,10 +40,9 @@ suite('UserDataProfileService (Common)', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, fileSystemProvider)); environmentService = new TestEnvironmentService(joinPath(ROOT, 'User')); - testObject = new InMemoryUserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService); + testObject = disposables.add(new InMemoryUserDataProfilesService(environmentService, fileService, disposables.add(new UriIdentityService(fileService)), logService)); }); - teardown(() => disposables.clear()); test('default profile', () => { assert.strictEqual(testObject.defaultProfile.isDefault, true); diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts index 1277db12632..48a68ee98a1 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { InMemoryStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage'; import { AbstractUserDataProfileStorageService, IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { InMemoryStorageService, loadKeyTargets, StorageTarget, TARGET_KEY } from 'vs/platform/storage/common/storage'; import { IUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class TestStorageDatabase extends InMemoryStorageDatabase { @@ -48,18 +48,17 @@ export class TestUserDataProfileStorageService extends AbstractUserDataProfileSt suite('ProfileStorageService', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const profile = toUserDataProfile('test', 'test', URI.file('foo'), URI.file('cache')); let testObject: TestUserDataProfileStorageService; let storage: Storage; setup(async () => { - testObject = disposables.add(new TestUserDataProfileStorageService(new InMemoryStorageService())); - storage = new Storage(await testObject.setupStorageDatabase(profile)); + testObject = disposables.add(new TestUserDataProfileStorageService(disposables.add(new InMemoryStorageService()))); + storage = disposables.add(new Storage(await testObject.setupStorageDatabase(profile))); await storage.init(); }); - teardown(() => disposables.clear()); test('read empty storage', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const actual = await testObject.readStorageData(profile); 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 3ed115ec932..515b2ce80da 100644 --- a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts +++ b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts @@ -9,13 +9,13 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -31,7 +31,7 @@ class TestEnvironmentService extends AbstractNativeEnvironmentService { suite('UserDataProfileMainService', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let testObject: UserDataProfilesMainService; let environmentService: TestEnvironmentService, stateService: StateService; @@ -42,14 +42,12 @@ suite('UserDataProfileMainService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, fileSystemProvider)); environmentService = new TestEnvironmentService(joinPath(ROOT, 'User')); - stateService = new StateService(SaveStrategy.DELAYED, environmentService, logService, fileService); + stateService = disposables.add(new StateService(SaveStrategy.DELAYED, environmentService, logService, fileService)); - testObject = new UserDataProfilesMainService(stateService, new UriIdentityService(fileService), environmentService, fileService, logService); + testObject = disposables.add(new UserDataProfilesMainService(stateService, disposables.add(new UriIdentityService(fileService)), environmentService, fileService, logService)); await stateService.init(); }); - teardown(() => disposables.clear()); - test('default profile', () => { assert.strictEqual(testObject.defaultProfile.isDefault, true); }); diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index bda030426b5..eb24d9ca6b5 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -26,7 +26,7 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; type IncompatibleSyncSourceClassification = { @@ -133,7 +133,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa private _onDidChangeConflicts = this._register(new Emitter()); readonly onDidChangeConflicts = this._onDidChangeConflicts.event; - private readonly localChangeTriggerThrottler = new ThrottledDelayer(50); + private readonly localChangeTriggerThrottler = this._register(new ThrottledDelayer(50)); private readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -153,7 +153,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa @IEnvironmentService protected readonly environmentService: IEnvironmentService, @IStorageService protected readonly storageService: IStorageService, @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService protected readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService protected readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @@ -770,7 +770,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa protected async backupLocal(content: string): Promise { const syncData: ISyncData = { version: this.version, content }; - return this.userDataSyncBackupStoreService.backup(this.syncResource.profile, this.resource, JSON.stringify(syncData)); + return this.userDataSyncLocalStoreService.writeResource(this.resource, JSON.stringify(syncData), new Date(), this.syncResource.profile.isDefault ? undefined : this.syncResource.profile.id); } async stop(): Promise { @@ -820,14 +820,14 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this._register(this.fileService.watch(this.extUri.dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } @@ -888,7 +888,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -896,7 +896,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @IConfigurationService configurationService: IConfigurationService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(file, syncResource, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } protected hasErrors(content: string, isArray: boolean): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index bd85da7b12c..be0c0e0bbb4 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -30,7 +30,7 @@ import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userData import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { IMergeResult as IExtensionMergeResult, merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { Change, IRemoteUserData, ISyncData, ISyncExtension, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ILocalSyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, ISyncData, ISyncExtension, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ILocalSyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; type IExtensionResourceMergeResult = IAcceptResult & IExtensionMergeResult; @@ -120,7 +120,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @@ -132,7 +132,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IUserDataProfileStorageService userDataProfileStorageService: IUserDataProfileStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.localExtensionsProvider = this.instantiationService.createInstance(LocalExtensionsProvider); this._register( Event.any( diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index a227f78e492..4c698553970 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -25,7 +25,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAcceptResult, IMergeResult, IResourcePreview, isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { edit } from 'vs/platform/userDataSync/common/content'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; -import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; @@ -83,7 +83,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IEnvironmentService environmentService: IEnvironmentService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -93,7 +93,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IUriIdentityService uriIdentityService: IUriIdentityService, @IInstantiationService instantiationService: IInstantiationService, ) { - super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.GlobalState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.localGlobalStateProvider = instantiationService.createInstance(LocalGlobalStateProvider); this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource))); this._register( diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 95e228c72fa..715707a19cc 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -22,7 +22,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM } from 'vs/platform/userDataSync/common/userDataSync'; interface ISyncContent { mac?: string; @@ -73,7 +73,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem profile: IUserDataProfile, collection: string | undefined, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -84,7 +84,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.keybindingsResource, { syncResource: SyncResource.Keybindings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); + super(profile.keybindingsResource, { syncResource: SyncResource.Keybindings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange())); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 42eeb9df44d..e16e0073a28 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -19,7 +19,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { getIgnoredSettings, isEmpty, merge, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest } from 'vs/platform/userDataSync/common/userDataSync'; interface ISettingsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -57,7 +57,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService configurationService: IConfigurationService, @@ -66,7 +66,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.settingsResource, { syncResource: SyncResource.Settings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); + super(profile.settingsResource, { syncResource: SyncResource.Settings, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService, uriIdentityService); } async getRemoteUserDataSyncConfiguration(manifest: IUserDataResourceManifest | null): Promise { @@ -319,12 +319,12 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement private async getIgnoredSettings(content?: string): Promise { if (!this._defaultIgnoredSettings) { this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings(); - const disposable = Event.any( + const disposable = this._register(Event.any( Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))), Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)))(() => { disposable.dispose(); this._defaultIgnoredSettings = undefined; - }); + })); } const defaultIgnoredSettings = await this._defaultIgnoredSettings; return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content); diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 7a6a086ca59..d89e5a95fe8 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -18,7 +18,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { areSame, IMergeResult as ISnippetsMergeResult, merge } from 'vs/platform/userDataSync/common/snippetsMerge'; -import { Change, IRemoteUserData, ISyncData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, ISyncData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ISnippetsResourcePreview extends IFileResourcePreview { previewResult: IMergeResult; @@ -44,14 +44,14 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD @IFileService fileService: IFileService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super({ syncResource: SyncResource.Snippets, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Snippets, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this.snippetsFolder = profile.snippetsHome; this._register(this.fileService.watch(environmentService.userRoamingDataHome)); this._register(this.fileService.watch(this.snippetsFolder)); diff --git a/src/vs/platform/userDataSync/common/tasksSync.ts b/src/vs/platform/userDataSync/common/tasksSync.ts index ae77bac5301..3c764f33bdd 100644 --- a/src/vs/platform/userDataSync/common/tasksSync.ts +++ b/src/vs/platform/userDataSync/common/tasksSync.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractFileSynchroniser, AbstractInitializer, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; interface ITasksSyncContent { tasks?: string; @@ -48,7 +48,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser profile: IUserDataProfile, collection: string | undefined, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @@ -58,7 +58,7 @@ export class TasksSynchroniser extends AbstractFileSynchroniser implements IUser @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(profile.tasksResource, { syncResource: SyncResource.Tasks, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super(profile.tasksResource, { syncResource: SyncResource.Tasks, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, isRemoteDataFromCurrentMachine: boolean, userDataSyncConfiguration: IUserDataSyncConfiguration): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts index d655cf5984e..eee01fe850c 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts @@ -15,7 +15,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { merge } from 'vs/platform/userDataSync/common/userDataProfilesManifestMerge'; -import { Change, IRemoteUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ISyncUserDataProfile, ISyncData, IUserDataResourceManifest, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, IRemoteUserData, IUserDataSyncLocalStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME, ISyncUserDataProfile, ISyncData, IUserDataResourceManifest, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; interface IUserDataProfileManifestResourceMergeResult extends IAcceptResult { readonly local: { added: ISyncUserDataProfile[]; removed: IUserDataProfile[]; updated: ISyncUserDataProfile[] }; @@ -44,14 +44,14 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i @IEnvironmentService environmentService: IEnvironmentService, @IStorageService storageService: IStorageService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super({ syncResource: SyncResource.Profiles, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.Profiles, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); this._register(userDataProfilesService.onDidChangeProfiles(() => this.triggerLocalChange())); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 245552ec53a..13f99fc4aa5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { distinct } from 'vs/base/common/arrays'; +import { VSBufferReadableStream } from 'vs/base/common/buffer'; import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -22,6 +23,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; import { IUserDataProfile, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; export function getDisallowedIgnoredSettings(): string[] { const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); @@ -167,6 +169,19 @@ export interface IUserDataManifest { readonly collections?: IUserDataCollectionManifest; } +export interface IUserDataActivityData { + resources?: { + [resourceId: string]: { created: number; content: string }[]; + }; + collections?: { + [collectionId: string]: { + resources?: { + [resourceId: string]: { created: number; content: string }[]; + } | undefined; + }; + }; +} + export interface IResourceRefHandle { ref: string; created: number; @@ -205,15 +220,17 @@ export interface IUserDataSyncStoreService { createCollection(headers?: IHeaders): Promise; deleteCollection(collection?: string, headers?: IHeaders): Promise; + getActivityData(): Promise; + clear(): Promise; } -export const IUserDataSyncBackupStoreService = createDecorator('IUserDataSyncBackupStoreService'); -export interface IUserDataSyncBackupStoreService { +export const IUserDataSyncLocalStoreService = createDecorator('IUserDataSyncLocalStoreService'); +export interface IUserDataSyncLocalStoreService { readonly _serviceBrand: undefined; - backup(profile: IUserDataProfile, resource: SyncResource, content: string): Promise; - getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise; - resolveContent(profile: IUserDataProfile, resource: SyncResource, ref: string): Promise; + writeResource(resource: ServerResource, content: string, cTime: Date, collection?: string, root?: URI): Promise; + getAllResourceRefs(resource: ServerResource, collection?: string, root?: URI): Promise; + resolveResourceContent(resource: ServerResource, ref: string, collection?: string, root?: URI): Promise; } //#endregion @@ -539,22 +556,22 @@ export interface IUserDataSyncService { hasLocalData(): Promise; hasPreviouslySynced(): Promise; - getRemoteProfiles(): Promise; - getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise; - getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise; - getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]>; - getMachineId(syncResourceHandle: ISyncResourceHandle): Promise; replace(syncResourceHandle: ISyncResourceHandle): Promise; + + saveRemoteActivityData(location: URI): Promise; + extractActivityData(activityDataResource: URI, location: URI): Promise; } export const IUserDataSyncResourceProviderService = createDecorator('IUserDataSyncResourceProviderService'); export interface IUserDataSyncResourceProviderService { _serviceBrand: any; getRemoteSyncedProfiles(): Promise; - getLocalSyncResourceHandles(syncResource: SyncResource, profile: IUserDataProfile): Promise; - getRemoteSyncResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise; + getLocalSyncedProfiles(location?: URI): Promise; + getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise; + getLocalSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile, location?: URI): Promise; getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]>; getMachineId(syncResourceHandle: ISyncResourceHandle): Promise; + getLocalSyncedMachines(location?: URI): Promise; resolveContent(resource: URI): Promise; resolveUserDataSyncResource(syncResourceHandle: ISyncResourceHandle): IUserDataSyncResource | undefined; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts similarity index 73% rename from src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts rename to src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts index dc19ee03ce9..28d2400cfee 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncBackupStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncLocalStoreService.ts @@ -12,10 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationResult, IFileService, IFileStat, toFileOperationResult } from 'vs/platform/files/common/files'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ALL_SYNC_RESOURCES, IResourceRefHandle, IUserDataSyncBackupStoreService, IUserDataSyncLogService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ALL_SYNC_RESOURCES, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncLogService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -export class UserDataSyncBackupStoreService extends Disposable implements IUserDataSyncBackupStoreService { +export class UserDataSyncLocalStoreService extends Disposable implements IUserDataSyncLocalStoreService { _serviceBrand: any; @@ -34,7 +34,7 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD for (const profile of this.userDataProfilesService.profiles) { for (const resource of ALL_SYNC_RESOURCES) { try { - await this.cleanUpBackup(this.getResourceBackupHome(profile, resource)); + await this.cleanUpBackup(this.getResourceBackupHome(resource, profile.isDefault ? undefined : profile.id)); } catch (error) { this.logService.error(error); } @@ -65,12 +65,12 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } } - async getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise { - const folder = this.getResourceBackupHome(profile, resource); + async getAllResourceRefs(resource: SyncResource, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resource, collection, root); try { const stat = await this.fileService.resolve(folder); if (stat.children) { - const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}(\.json)?$/.test(stat.name)).sort().reverse(); + const all = stat.children.filter(stat => stat.isFile && !stat.name.startsWith('lastSync')).sort().reverse(); return all.map(stat => ({ ref: stat.name, created: this.getCreationTime(stat) @@ -84,8 +84,8 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD return []; } - async resolveContent(profile: IUserDataProfile, resourceKey: SyncResource, ref: string): Promise { - const folder = this.getResourceBackupHome(profile, resourceKey); + async resolveResourceContent(resourceKey: SyncResource, ref: string, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resourceKey, collection, root); const file = joinPath(folder, ref); try { const content = await this.fileService.readFile(file); @@ -96,21 +96,18 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } } - async backup(profile: IUserDataProfile, resourceKey: SyncResource, content: string): Promise { - const folder = this.getResourceBackupHome(profile, resourceKey); - const resource = joinPath(folder, `${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}.json`); + async writeResource(resourceKey: SyncResource, content: string, cTime: Date, collection?: string, root?: URI): Promise { + const folder = this.getResourceBackupHome(resourceKey, collection, root); + const resource = joinPath(folder, `${toLocalISOString(cTime).replace(/-|:|\.\d+Z$/g, '')}.json`); try { await this.fileService.writeFile(resource, VSBuffer.fromString(content)); } catch (e) { this.logService.error(e); } - try { - this.cleanUpBackup(folder); - } catch (e) { /* Ignore */ } } - private getResourceBackupHome(profile: IUserDataProfile, resource: SyncResource): URI { - return joinPath(this.environmentService.userDataSyncHome, ...(profile.isDefault ? [resource] : [profile.id, resource])); + private getResourceBackupHome(resource: SyncResource, collection?: string, root: URI = this.environmentService.userDataSyncHome): URI { + return joinPath(root, ...(collection ? [collection, resource] : [resource])); } private async cleanUpBackup(folder: URI): Promise { @@ -142,7 +139,7 @@ export class UserDataSyncBackupStoreService extends Disposable implements IUserD } private getCreationTime(stat: IFileStat) { - return stat.ctime || new Date( + return new Date( parseInt(stat.name.substring(0, 4)), parseInt(stat.name.substring(4, 6)) - 1, parseInt(stat.name.substring(6, 8)), diff --git a/src/vs/platform/userDataSync/common/userDataSyncLog.ts b/src/vs/platform/userDataSync/common/userDataSyncLog.ts index d458fc3e8db..4dffdbc947e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncLog.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncLog.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { IUserDataSyncLogService, USER_DATA_SYNC_LOG_ID } from 'vs/platform/userDataSync/common/userDataSync'; @@ -14,9 +16,10 @@ export class UserDataSyncLogService extends AbstractLogger implements IUserDataS constructor( @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); - this.logger = this._register(loggerService.createLogger(USER_DATA_SYNC_LOG_ID, { name: localize('userDataSyncLog', "Settings Sync") })); + this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${USER_DATA_SYNC_LOG_ID}.log`), { id: USER_DATA_SYNC_LOG_ID, name: localize('userDataSyncLog', "Settings Sync") })); } trace(message: string, ...args: any[]): void { diff --git a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts index 6460ed5792d..ad3f8f63a1c 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts @@ -16,14 +16,14 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; -interface IMachineData { +export interface IMachineData { id: string; name: string; disabled?: boolean; platform?: string; } -interface IMachinesData { +export interface IMachinesData { version: number; machines: IMachineData[]; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts b/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts index 7086de977e6..e31e85e0e57 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncResourceProvider.ts @@ -11,7 +11,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { getServiceMachineId } from 'vs/platform/externalServices/common/serviceMachineId'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncStoreService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncResourceProviderService, ISyncUserDataProfile, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncStoreService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncResourceProviderService, ISyncUserDataProfile, CONFIG_SYNC_KEYBINDINGS_PER_PLATFORM, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { isSyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { parseSnippets } from 'vs/platform/userDataSync/common/snippetsSync'; @@ -24,6 +24,8 @@ import { LocalGlobalStateProvider, stringify as stringifyGlobalState } from 'vs/ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { parseUserDataProfilesManifest, stringifyLocalProfiles } from 'vs/platform/userDataSync/common/userDataProfilesManifestSync'; import { toFormattedString } from 'vs/base/common/jsonFormatter'; +import { trim } from 'vs/base/common/strings'; +import { IMachinesData, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; interface ISyncResourceUriInfo { readonly remote: boolean; @@ -32,6 +34,7 @@ interface ISyncResourceUriInfo { readonly collection: string | undefined; readonly ref: string | undefined; readonly node: string | undefined; + readonly location: URI | undefined; } export class UserDataSyncResourceProviderService implements IUserDataSyncResourceProviderService { @@ -46,7 +49,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, - @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, + @IUserDataSyncLocalStoreService private readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -68,7 +71,31 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return []; } - async getRemoteSyncResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + async getLocalSyncedProfiles(location?: URI): Promise { + const refs = await this.userDataSyncLocalStoreService.getAllResourceRefs(SyncResource.Profiles, undefined, location); + if (refs.length) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent(SyncResource.Profiles, refs[0].ref, undefined, location); + if (content) { + const syncData = this.parseSyncData(content, SyncResource.Profiles); + return parseUserDataProfilesManifest(syncData); + } + } + return []; + } + + async getLocalSyncedMachines(location?: URI): Promise { + const refs = await this.userDataSyncLocalStoreService.getAllResourceRefs('machines', undefined, location); + if (refs.length) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent('machines', refs[0].ref, undefined, location); + if (content) { + const machinesData: IMachinesData = JSON.parse(content); + return machinesData.machines.map(m => ({ ...m, isCurrent: false })); + } + } + return []; + } + + async getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { const handles = await this.userDataSyncStoreService.getAllResourceRefs(syncResource, profile?.collection); return handles.map(({ created, ref }) => ({ created, @@ -76,6 +103,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: true, syncResource, profile: profile?.id ?? this.userDataProfilesService.defaultProfile.id, + location: undefined, collection: profile?.collection, ref, node: undefined, @@ -83,17 +111,18 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc })); } - async getLocalSyncResourceHandles(syncResource: SyncResource, profile: IUserDataProfile): Promise { - const handles = await this.userDataSyncBackupStoreService.getAllRefs(profile, syncResource); + async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile, location?: URI): Promise { + const handles = await this.userDataSyncLocalStoreService.getAllResourceRefs(syncResource, profile?.collection, location); return handles.map(({ created, ref }) => ({ created, uri: this.toUri({ remote: false, syncResource, - profile: profile.id, - collection: undefined, + profile: profile?.id ?? this.userDataProfilesService.defaultProfile.id, + collection: profile?.collection, ref, node: undefined, + location, }) })); } @@ -138,6 +167,18 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc } return undefined; } + + if (resolved.location) { + if (resolved.ref) { + const content = await this.userDataSyncLocalStoreService.resolveResourceContent(resolved.syncResource, resolved.ref, resolved.collection, resolved.location); + if (content) { + const syncData = this.parseSyncData(content, resolved.syncResource); + return syncData?.machineId; + } + } + return undefined; + } + return getServiceMachineId(this.environmentService, this.fileService, this.storageService); } @@ -152,7 +193,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc } if (resolved.ref) { - const content = await this.getContentFromStore(resolved.remote, resolved.syncResource, resolved.profile, resolved.collection, resolved.ref); + const content = await this.getContentFromStore(resolved.remote, resolved.syncResource, resolved.collection, resolved.ref, resolved.location); if (resolved.node && content) { return this.resolveNodeContent(resolved.syncResource, content, resolved.node); } @@ -166,16 +207,12 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return null; } - private async getContentFromStore(remote: boolean, syncResource: SyncResource, profileId: string, collection: string | undefined, ref: string): Promise { + private async getContentFromStore(remote: boolean, syncResource: SyncResource, collection: string | undefined, ref: string, location?: URI): Promise { if (remote) { const { content } = await this.getUserData(syncResource, ref, collection); return content; } - const profile = this.userDataProfilesService.profiles.find(p => p.id === profileId); - if (profile) { - return this.userDataSyncBackupStoreService.resolveContent(profile, syncResource, ref); - } - return null; + return this.userDataSyncLocalStoreService.resolveResourceContent(syncResource, ref, collection, location); } private resolveNodeContent(syncResource: SyncResource, content: string, node: string): string | null { @@ -280,6 +317,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.Extensions, profile: profile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -308,6 +346,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.GlobalState, profile: profile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -335,6 +374,7 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc remote: false, syncResource: SyncResource.Profiles, profile: this.userDataProfilesService.defaultProfile.id, + location: undefined, collection: undefined, ref: undefined, node: undefined, @@ -356,10 +396,15 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc private toUri(syncResourceUriInfo: ISyncResourceUriInfo): URI { const authority = syncResourceUriInfo.remote ? UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY : UserDataSyncResourceProviderService.LOCAL_BACKUP_AUTHORITY; - const paths = [ - syncResourceUriInfo.syncResource, - syncResourceUriInfo.profile, - ]; + const paths = []; + if (syncResourceUriInfo.location) { + paths.push(`scheme:${syncResourceUriInfo.location.scheme}`); + paths.push(`authority:${syncResourceUriInfo.location.authority}`); + paths.push(trim(syncResourceUriInfo.location.path, '/')); + } + paths.push(`syncResource:${syncResourceUriInfo.syncResource}`); + paths.push(`profile:${syncResourceUriInfo.profile}`); + paths.push(syncResourceUriInfo.profile); if (syncResourceUriInfo.collection) { paths.push(`collection:${syncResourceUriInfo.collection}`); } @@ -369,16 +414,13 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc if (syncResourceUriInfo.node) { paths.push(syncResourceUriInfo.node); } - return this.extUri.joinPath(URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority, path: `/` }), ...paths); + return this.extUri.joinPath(URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority, path: `/`, query: syncResourceUriInfo.location?.query, fragment: syncResourceUriInfo.location?.fragment }), ...paths); } private resolveUri(uri: URI): ISyncResourceUriInfo | undefined { if (uri.scheme !== USER_DATA_SYNC_SCHEME) { return undefined; } - if (uri.authority !== UserDataSyncResourceProviderService.LOCAL_BACKUP_AUTHORITY && uri.authority !== UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY) { - return undefined; - } const paths: string[] = []; while (uri.path !== '/') { paths.unshift(this.extUri.basename(uri)); @@ -388,28 +430,42 @@ export class UserDataSyncResourceProviderService implements IUserDataSyncResourc return undefined; } const remote = uri.authority === UserDataSyncResourceProviderService.REMOTE_BACKUP_AUTHORITY; - const syncResource = paths.shift()! as SyncResource; - const profile = paths.shift()!; + let scheme: string | undefined; + let authority: string | undefined; + const locationPaths: string[] = []; + let syncResource: SyncResource | undefined; + let profile: string | undefined; let collection: string | undefined; let ref: string | undefined; let node: string | undefined; while (paths.length) { const path = paths.shift()!; - if (path.startsWith('collection:')) { + if (path.startsWith('scheme:')) { + scheme = path.substring('scheme:'.length); + } else if (path.startsWith('authority:')) { + authority = path.substring('authority:'.length); + } else if (path.startsWith('syncResource:')) { + syncResource = path.substring('syncResource:'.length) as SyncResource; + } else if (path.startsWith('profile:')) { + profile = path.substring('profile:'.length); + } else if (path.startsWith('collection:')) { collection = path.substring('collection:'.length); } else if (path.startsWith('ref:')) { ref = path.substring('ref:'.length); + } else if (!syncResource) { + locationPaths.push(path); } else { node = path; } } return { remote, - syncResource, - profile, + syncResource: syncResource!, + profile: profile!, collection, ref, node, + location: scheme && authority !== undefined ? this.extUri.joinPath(URI.from({ scheme, authority, query: uri.query, fragment: uri.fragment, path: '/' }), ...locationPaths) : undefined }; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index a58320c263e..59ac96cda5a 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -30,7 +31,7 @@ import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, ISyncUserDataProfile, IUserDataManifest, IUserDataResourceManifest, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, - MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService + MergeState, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreError, USER_DATA_SYNC_CONFIGURATION_SCOPE, IUserDataSyncResourceProviderService, IUserDataActivityData, IUserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSync'; type SyncErrorClassification = { @@ -81,6 +82,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private activeProfileSynchronizers = new Map(); constructor( + @IFileService private readonly fileService: IFileService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -90,6 +92,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IUserDataSyncResourceProviderService private readonly userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, + @IUserDataSyncLocalStoreService private readonly userDataSyncLocalStoreService: IUserDataSyncLocalStoreService, ) { super(); this._status = userDataSyncStoreManagementService.userDataSyncStore ? SyncStatus.Idle : SyncStatus.Uninitialized; @@ -337,26 +340,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ }); } - getRemoteProfiles(): Promise { - return this.userDataSyncResourceProviderService.getRemoteSyncedProfiles(); - } - - getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - return this.userDataSyncResourceProviderService.getRemoteSyncResourceHandles(syncResource, profile); - } - - async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise { - return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile ?? this.userDataProfilesService.defaultProfile); - } - - async getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]> { - return this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle); - } - - async getMachineId(syncResourceHandle: ISyncResourceHandle): Promise { - return this.userDataSyncResourceProviderService.getMachineId(syncResourceHandle); - } - async hasLocalData(): Promise { const result = await this.performAction(this.userDataProfilesService.defaultProfile, async synchronizer => { // skip global state synchronizer @@ -437,6 +420,35 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } + async saveRemoteActivityData(location: URI): Promise { + this.checkEnablement(); + const data = await this.userDataSyncStoreService.getActivityData(); + await this.fileService.writeFile(location, data); + } + + async extractActivityData(activityDataResource: URI, location: URI): Promise { + const content = (await this.fileService.readFile(activityDataResource)).value.toString(); + const activityData: IUserDataActivityData = JSON.parse(content); + + if (activityData.resources) { + for (const resource in activityData.resources) { + for (const version of activityData.resources[resource]) { + await this.userDataSyncLocalStoreService.writeResource(resource as SyncResource, version.content, new Date(version.created * 1000), undefined, location); + } + } + } + + if (activityData.collections) { + for (const collection in activityData.collections) { + for (const resource in activityData.collections[collection].resources) { + for (const version of activityData.collections[collection].resources?.[resource] ?? []) { + await this.userDataSyncLocalStoreService.writeResource(resource as SyncResource, version.content, new Date(version.created * 1000), collection, location); + } + } + } + } + } + private async performAction(profile: IUserDataProfile, action: (synchroniser: IUserDataSynchroniser) => Promise): Promise { const disposables = new DisposableStore(); try { diff --git a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts index 39c6dd16b90..94e3fdd0028 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts @@ -9,10 +9,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; -import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataManualSyncTask, IUserDataSyncResourceConflicts, IUserDataSyncResourceError, IUserDataSyncResource, ISyncResourceHandle, IUserDataSyncTask, IUserDataSyncService, - SyncResource, SyncStatus, UserDataSyncError, ISyncUserDataProfile + SyncResource, SyncStatus, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; type ManualSyncTaskEvent = { manualSyncTaskId: string; data: T }; @@ -76,13 +76,10 @@ export class UserDataSyncChannel implements IServerChannel { case 'hasLocalData': return this.service.hasLocalData(); case 'resolveContent': return this.service.resolveContent(URI.revive(args[0])); case 'accept': return this.service.accept(reviewSyncResource(args[0], this.userDataProfilesService), URI.revive(args[1]), args[2], args[3]); - case 'getRemoteProfiles': return this.service.getRemoteProfiles(); - case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0], args[1] ? reviveProfile(args[1], this.userDataProfilesService.profilesHome.scheme) : undefined); - case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0], args[1]); case 'replace': return this.service.replace(reviewSyncResourceHandle(args[0])); - case 'getAssociatedResources': return this.service.getAssociatedResources(reviewSyncResourceHandle(args[0])); - case 'getMachineId': return this.service.getMachineId(reviewSyncResourceHandle(args[0])); case 'cleanUpRemoteData': return this.service.cleanUpRemoteData(); + case 'getRemoteActivityData': return this.service.saveRemoteActivityData(URI.revive(args[0])); + case 'extractActivityData': return this.service.extractActivityData(URI.revive(args[0]), URI.revive(args[1])); case 'createManualSyncTask': return this.createManualSyncTask(); } @@ -224,29 +221,6 @@ export class UserDataSyncChannelClient extends Disposable implements IUserDataSy return this.channel.call('resolveContent', [resource]); } - getRemoteProfiles(): Promise { - return this.channel.call('getRemoteProfiles'); - } - - async getLocalSyncResourceHandles(syncResource: SyncResource, profile?: IUserDataProfile): Promise { - const handles = await this.channel.call('getLocalSyncResourceHandles', [syncResource, profile]); - return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); - } - - async getRemoteSyncResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - const handles = await this.channel.call('getRemoteSyncResourceHandles', [syncResource, profile]); - return handles.map(({ created, uri }) => ({ created, uri: URI.revive(uri) })); - } - - async getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI; comparableResource: URI }[]> { - const result = await this.channel.call<{ resource: URI; comparableResource: URI }[]>('getAssociatedResources', [syncResourceHandle]); - return result.map(({ resource, comparableResource }) => ({ resource: URI.revive(resource), comparableResource: URI.revive(comparableResource) })); - } - - getMachineId(syncResourceHandle: ISyncResourceHandle): Promise { - return this.channel.call('getMachineId', [syncResourceHandle]); - } - cleanUpRemoteData(): Promise { return this.channel.call('cleanUpRemoteData'); } @@ -255,6 +229,14 @@ export class UserDataSyncChannelClient extends Disposable implements IUserDataSy return this.channel.call('replace', [syncResourceHandle]); } + saveRemoteActivityData(location: URI): Promise { + return this.channel.call('getRemoteActivityData', [location]); + } + + extractActivityData(activityDataResource: URI, location: URI): Promise { + return this.channel.call('extractActivityData', [activityDataResource, location]); + } + private async updateStatus(status: SyncStatus): Promise { this._status = status; this._onDidChangeStatus.fire(status); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index c3a47d18c50..046f969de91 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -20,10 +20,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/productService'; -import { asJson, asText, asTextOrError, IRequestService, isSuccess as isSuccessContext } from 'vs/platform/request/common/request'; +import { asJson, asText, asTextOrError, hasNoContent, IRequestService, isSuccess, isSuccess as isSuccessContext } from 'vs/platform/request/common/request'; import { getServiceMachineId } from 'vs/platform/externalServices/common/serviceMachineId'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { HEADER_EXECUTION_ID, HEADER_OPERATION_ID, IAuthenticationProvider, IResourceRefHandle, IUserData, IUserDataManifest, IUserDataSyncLogService, IUserDataSyncStore, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, ServerResource, SYNC_SERVICE_URL_TYPE, UserDataSyncErrorCode, UserDataSyncStoreError, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; +import { VSBufferReadableStream } from 'vs/base/common/buffer'; const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; const SYNC_PREVIOUS_STORE = 'sync.previous.store'; @@ -457,6 +458,27 @@ export class UserDataSyncStoreClient extends Disposable { this.clearSession(); } + async getActivityData(): Promise { + if (!this.userDataSyncStoreUrl) { + throw new Error('No settings sync store url configured.'); + } + + const url = joinPath(this.userDataSyncStoreUrl, 'download').toString(); + const headers: IHeaders = {}; + + const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None); + + if (!isSuccess(context)) { + throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]); + } + + if (hasNoContent(context)) { + throw new UserDataSyncStoreError('Empty response', url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]); + } + + return context.stream; + } + private getResourceUrl(userDataSyncStoreUrl: URI, collection: string | undefined, resource: ServerResource): URI { return collection ? joinPath(userDataSyncStoreUrl, 'collection', collection, 'resource', resource) : joinPath(userDataSyncStoreUrl, 'resource', resource); } diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 6cf08639193..9e0c8296474 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -15,17 +14,23 @@ import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalS import { IGlobalState, ISyncData, IUserDataSyncStoreService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('GlobalStateSync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let testClient: UserDataSyncClient; let client2: UserDataSyncClient; let testObject: GlobalStateSynchroniser; + teardown(async () => { + await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); @@ -35,11 +40,6 @@ suite('GlobalStateSync', () => { await client2.setUp(true); }); - teardown(async () => { - await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('when global state does not exist', () => runWithFakedTimers({ useFakeTimers: true }, async () => { assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await testClient.getResourceManifest(); diff --git a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts index f70f7a82d69..935c553a872 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsMerge.test.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { TestUserDataSyncUtilService } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; suite('KeybindingsMerge - No Conflicts', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('merge when local and remote are same with one entry', async () => { const localContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const remoteContent = stringify([{ key: 'alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); @@ -542,7 +545,7 @@ suite('KeybindingsMerge - No Conflicts', () => { ]`); }); - test('merge when local and remote has moved forwareded with conflicts', async () => { + test('merge when local and remote has moved forwareded with conflicts (2)', async () => { const baseContent = stringify([ { key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }, { key: 'alt+c', command: '-a' }, diff --git a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts index 44fb14361a7..3e241a7bd9d 100644 --- a/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/keybindingsSync.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -15,22 +15,23 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('KeybindingsSync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; let testObject: KeybindingsSynchroniser; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); testObject = client.getSynchronizer(SyncResource.Keybindings) as KeybindingsSynchroniser; }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); test('when keybindings file does not exist', async () => { const fileService = client.instantiationService.get(IFileService); diff --git a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts index 8ce9aadef2d..e6908111ebc 100644 --- a/src/vs/platform/userDataSync/test/common/settingsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsSync.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IFileService } from 'vs/platform/files/common/files'; @@ -19,11 +19,16 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('SettingsSync - Auto', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; let testObject: SettingsSynchroniser; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { Registry.as(Extensions.Configuration).registerConfiguration({ 'id': 'settingsSync', @@ -44,11 +49,6 @@ suite('SettingsSync - Auto', () => { testObject = client.getSynchronizer(SyncResource.Settings) as SettingsSynchroniser; }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('when settings file does not exist', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const fileService = client.instantiationService.get(IFileService); const settingResource = client.instantiationService.get(IUserDataProfilesService).defaultProfile.settingsResource; @@ -529,23 +529,22 @@ suite('SettingsSync - Auto', () => { suite('SettingsSync - Manual', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; let testObject: SettingsSynchroniser; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); testObject = client.getSynchronizer(SyncResource.Settings) as SettingsSynchroniser; }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - - test('do not sync ignored settings', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const settingsContent = `{ diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 2b39ada8d1c..531665ceddd 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -6,9 +6,9 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { IStringDictionary } from 'vs/base/common/collections'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { dirname, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -148,13 +148,18 @@ const globalSnippet = `{ suite('SnippetsSync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let testClient: UserDataSyncClient; let client2: UserDataSyncClient; let testObject: SnippetsSynchroniser; + teardown(async () => { + await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); @@ -164,11 +169,6 @@ suite('SnippetsSync', () => { await client2.setUp(true); }); - teardown(async () => { - await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('when snippets does not exist', async () => { const fileService = testClient.instantiationService.get(IFileService); const snippetsResource = testClient.instantiationService.get(IUserDataProfilesService).defaultProfile.snippetsHome; diff --git a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts index 1db10faccdc..d226ceb9dba 100644 --- a/src/vs/platform/userDataSync/test/common/synchronizer.test.ts +++ b/src/vs/platform/userDataSync/test/common/synchronizer.test.ts @@ -8,10 +8,10 @@ import { Barrier } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -177,20 +177,20 @@ class TestSynchroniser extends AbstractSynchroniser { suite('TestSynchronizer - Auto Sync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(); }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('status is syncing', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); @@ -484,20 +484,20 @@ suite('TestSynchronizer - Auto Sync', () => { suite('TestSynchronizer - Manual Sync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(); }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('preview', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); testObject.syncResult = { hasConflicts: false, hasError: false }; @@ -1062,20 +1062,20 @@ suite('TestSynchronizer - Manual Sync', () => { }); suite('TestSynchronizer - Last Sync Data', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(); }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('last sync data is null when not synced before', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const testObject: TestSynchroniser = disposableStore.add(client.instantiationService.createInstance(TestSynchroniser, { syncResource: SyncResource.Settings, profile: client.instantiationService.get(IUserDataProfilesService).defaultProfile }, undefined)); diff --git a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts index 01be339a733..52669939bef 100644 --- a/src/vs/platform/userDataSync/test/common/tasksSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/tasksSync.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -15,23 +15,23 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('TasksSync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let client: UserDataSyncClient; let testObject: TasksSynchroniser; + teardown(async () => { + await client.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { client = disposableStore.add(new UserDataSyncClient(server)); await client.setUp(true); testObject = client.getSynchronizer(SyncResource.Tasks) as TasksSynchroniser; }); - teardown(async () => { - await client.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('when tasks file does not exist', async () => { const fileService = client.instantiationService.get(IFileService); const tasksResource = client.instantiationService.get(IUserDataProfilesService).defaultProfile.tasksResource; diff --git a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts index 7f6e72d2ff2..47cde8003c7 100644 --- a/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataAutoSyncService.test.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -29,9 +28,7 @@ class TestUserDataAutoSyncService extends UserDataAutoSyncService { suite('UserDataAutoSyncService', () => { - const disposableStore = new DisposableStore(); - - teardown(() => disposableStore.clear()); + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); test('test auto sync with sync resource change triggers sync', async () => { await runWithFakedTimers({}, async () => { @@ -382,7 +379,7 @@ suite('UserDataAutoSyncService', () => { const errorPromise = Event.toPromise(testObject.onError); await testObject.sync(); - const e = await Promise.race([errorPromise, timeout(0)]); + const e = await errorPromise; assert.ok(e instanceof UserDataAutoSyncError); assert.deepStrictEqual((e).code, UserDataSyncErrorCode.SessionExpired); assert.deepStrictEqual(target.requests, [ diff --git a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts index 502dc913380..745eac0ae58 100644 --- a/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataProfilesManifestSync.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfilesManifestSynchroniser } from 'vs/platform/userDataSync/common/userDataProfilesManifestSync'; import { ISyncData, ISyncUserDataProfile, IUserDataSyncStoreService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; @@ -12,13 +12,18 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('UserDataProfilesManifestSync', () => { - const disposableStore = new DisposableStore(); const server = new UserDataSyncTestServer(); let testClient: UserDataSyncClient; let client2: UserDataSyncClient; let testObject: UserDataProfilesManifestSynchroniser; + teardown(async () => { + await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); + }); + + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + setup(async () => { testClient = disposableStore.add(new UserDataSyncClient(server)); await testClient.setUp(true); @@ -28,11 +33,6 @@ suite('UserDataProfilesManifestSync', () => { await client2.setUp(true); }); - teardown(async () => { - await testClient.instantiationService.get(IUserDataSyncStoreService).clear(); - disposableStore.clear(); - }); - test('when profiles does not exist', async () => { assert.deepStrictEqual(await testObject.getLastSyncUserData(), null); let manifest = await testClient.getResourceManifest(); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index c9398a04e20..a1ba702c728 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -34,9 +34,9 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser, IUserDataResourceManifest, IUserDataCollectionManifest, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, getDefaultIgnoredSettings, IUserData, IUserDataSyncLocalStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration, ServerResource, SyncResource, IUserDataSynchroniser, IUserDataResourceManifest, IUserDataCollectionManifest, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -56,7 +56,7 @@ export class UserDataSyncClient extends Disposable { } async setUp(empty: boolean = false): Promise { - registerConfiguration(); + this._register(registerConfiguration()); const logService = this.instantiationService.stub(ILogService, new NullLogService()); @@ -83,17 +83,17 @@ export class UserDataSyncClient extends Disposable { }); const fileService = this._register(new FileService(logService)); - fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); - fileService.registerProvider(USER_DATA_SYNC_SCHEME, new InMemoryFileSystemProvider()); + this._register(fileService.registerProvider(Schemas.inMemory, this._register(new InMemoryFileSystemProvider()))); + this._register(fileService.registerProvider(USER_DATA_SYNC_SCHEME, this._register(new InMemoryFileSystemProvider()))); this.instantiationService.stub(IFileService, fileService); - const uriIdentityService = this.instantiationService.createInstance(UriIdentityService); + const uriIdentityService = this._register(this.instantiationService.createInstance(UriIdentityService)); this.instantiationService.stub(IUriIdentityService, uriIdentityService); - const userDataProfilesService = new InMemoryUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + const userDataProfilesService = this._register(new InMemoryUserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); this.instantiationService.stub(IUserDataProfilesService, userDataProfilesService); - const storageService = new TestStorageService(userDataProfilesService.defaultProfile); + const storageService = this._register(new TestStorageService(userDataProfilesService.defaultProfile)); this.instantiationService.stub(IStorageService, this._register(storageService)); this.instantiationService.stub(IUserDataProfileStorageService, this._register(new TestUserDataProfileStorageService(storageService))); @@ -113,7 +113,7 @@ export class UserDataSyncClient extends Disposable { this.instantiationService.stub(IUserDataSyncAccountService, userDataSyncAccountService); this.instantiationService.stub(IUserDataSyncMachinesService, this._register(this.instantiationService.createInstance(UserDataSyncMachinesService))); - this.instantiationService.stub(IUserDataSyncBackupStoreService, this._register(this.instantiationService.createInstance(UserDataSyncBackupStoreService))); + this.instantiationService.stub(IUserDataSyncLocalStoreService, this._register(this.instantiationService.createInstance(UserDataSyncLocalStoreService))); this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService()); this.instantiationService.stub(IUserDataSyncEnablementService, this._register(this.instantiationService.createInstance(UserDataSyncEnablementService))); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts index 42cfe4a1e47..665d09ed41e 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { dirname, joinPath } from 'vs/base/common/resources'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -15,9 +15,7 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('UserDataSyncService', () => { - const disposableStore = new DisposableStore(); - - teardown(() => disposableStore.clear()); + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); test('test first time sync ever', async () => { // Setup the client diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts index 83a6a6f0f36..42f3abd7e56 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts @@ -8,9 +8,9 @@ import { timeout } from 'vs/base/common/async'; import { newWriteableBufferStream } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -20,9 +20,7 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData suite('UserDataSyncStoreService', () => { - const disposableStore = new DisposableStore(); - - teardown(() => disposableStore.clear()); + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); test('test read manifest for the first time', async () => { // Setup the client diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index ab886d7dc8c..68455a45e42 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -7,7 +7,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as languages from 'vs/editor/common/languages'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -287,13 +286,15 @@ export class MainThreadCommentController implements ICommentController { this._commentService.updateComments(this._uniqueId, { added: [thread], removed: [], - changed: [] + changed: [], + pending: [] }); } else { this._commentService.updateNotebookComments(this._uniqueId, { added: [thread as MainThreadCommentThread], removed: [], - changed: [] + changed: [], + pending: [] }); } @@ -311,13 +312,15 @@ export class MainThreadCommentController implements ICommentController { this._commentService.updateComments(this._uniqueId, { added: [], removed: [], - changed: [thread] + changed: [thread], + pending: [] }); } else { this._commentService.updateNotebookComments(this._uniqueId, { added: [], removed: [], - changed: [thread as MainThreadCommentThread] + changed: [thread as MainThreadCommentThread], + pending: [] }); } @@ -332,13 +335,15 @@ export class MainThreadCommentController implements ICommentController { this._commentService.updateComments(this._uniqueId, { added: [], removed: [thread], - changed: [] + changed: [], + pending: [] }); } else { this._commentService.updateNotebookComments(this._uniqueId, { added: [], removed: [thread as MainThreadCommentThread], - changed: [] + changed: [], + pending: [] }); } } @@ -502,8 +507,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments })); } - $registerCommentController(handle: number, id: string, label: string): void { - const providerId = generateUuid(); + $registerCommentController(handle: number, id: string, label: string, extensionId: string): void { + const providerId = `${label}-${extensionId}`; this._handlers.set(handle, providerId); const provider = new MainThreadCommentController(this._proxy, this._commentService, handle, providerId, id, label, {}); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 95d016287a2..7e68b4409db 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -343,7 +343,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostTerminalService.getDefaultShell(false); }, get onDidChangeShell() { - checkProposedApiEnabled(extension, 'envShellEvent'); return extHostTerminalService.onDidChangeShell; }, get isTelemetryEnabled() { @@ -1298,9 +1297,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // this needs to be updated whenever the API proposal changes _version: 1, - registerInteractiveEditorSessionProvider(provider: vscode.InteractiveEditorSessionProvider) { + registerInteractiveEditorSessionProvider(provider: vscode.InteractiveEditorSessionProvider, metadata?: vscode.InteractiveEditorSessionProviderMetadata) { checkProposedApiEnabled(extension, 'interactive'); - return extHostInteractiveEditor.registerProvider(extension, provider); + return extHostInteractiveEditor.registerProvider(extension, provider, metadata = { label: metadata?.label ?? extension.displayName ?? extension.name }); }, registerInteractiveSessionProvider(id: string, provider: vscode.InteractiveSessionProvider) { checkProposedApiEnabled(extension, 'interactive'); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a3063e71595..eaf7745216b 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -135,7 +135,7 @@ export type CommentThreadChanges = Partial<{ }>; export interface MainThreadCommentsShape extends IDisposable { - $registerCommentController(handle: number, id: string, label: string): void; + $registerCommentController(handle: number, id: string, label: string, extensionId: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean): languages.CommentThread | undefined; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 84545808c0f..f4f7c8ee4a7 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -14,7 +14,7 @@ import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticToken import { validateWhenClauses } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { matchesSomeScheme } from 'vs/platform/opener/common/opener'; -import { ICallHierarchyItemDto, IIncomingCallDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ICallHierarchyItemDto, IIncomingCallDto, IInlineValueContextDto, IOutgoingCallDto, IRawColorInfo, ITypeHierarchyItemDto, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; @@ -385,7 +385,11 @@ const newCommands: ApiCommand[] = [ // --- debug support new ApiCommand( 'vscode.executeInlineValueProvider', '_executeInlineValueProvider', 'Execute inline value provider', - [ApiCommandArgument.Uri, ApiCommandArgument.Range], + [ + ApiCommandArgument.Uri, + ApiCommandArgument.Range, + new ApiCommandArgument('context', 'An InlineValueContext', v => v && typeof v.frameId === 'number' && v.stoppedLocation instanceof types.Range, v => typeConverters.InlineValueContext.from(v)) + ], new ApiCommandResult('A promise that resolves to an array of InlineValue objects', result => { return result.map(typeConverters.InlineValue.to); }) diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index c1208be0537..f621a1d123c 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -592,7 +592,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo private _id: string, private _label: string ) { - proxy.$registerCommentController(this.handle, _id, _label); + proxy.$registerCommentController(this.handle, _id, _label, this._extension.identifier.value); const that = this; this.value = Object.freeze({ diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 0a0eebdd8a5..99fccebaee0 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -44,7 +44,7 @@ import { Schemas } from 'vs/base/common/network'; import { IResolveAuthorityResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; import { StopWatch } from 'vs/base/common/stopwatch'; -import { setTimeout0 } from 'vs/base/common/platform'; +import { isCI, setTimeout0 } from 'vs/base/common/platform'; import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -164,6 +164,12 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._activationEventsReader, filterExtensions(this._globalRegistry, myExtensionsSet) ); + + if (isCI) { + this._logService.info(`Creating extension host with the following global extensions: ${printExtIds(this._globalRegistry)}`); + this._logService.info(`Creating extension host with the following local extensions: ${printExtIds(this._myRegistry)}`); + } + this._storage = new ExtHostStorage(this._extHostContext, this._logService); this._secretState = new ExtHostSecretState(this._extHostContext); this._storagePath = storagePath; @@ -734,8 +740,18 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return new Promise((resolve, reject) => { const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { if (error) { + if (isCI) { + this._logService.error(`Test runner called back with error`, error); + } reject(error); } else { + if (isCI) { + if (failures) { + this._logService.info(`Test runner called back with ${failures} failures.`); + } else { + this._logService.info(`Test runner called back with successful outcome.`); + } + } resolve((typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); } }; @@ -748,9 +764,15 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme if (runResult && runResult.then) { runResult .then(() => { + if (isCI) { + this._logService.info(`Test runner finished successfully.`); + } resolve(0); }) .catch((err: unknown) => { + if (isCI) { + this._logService.error(`Test runner finished with error`, err); + } reject(err instanceof Error && err.stack ? err.stack : String(err)); }); } @@ -963,6 +985,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions()); this._myRegistry.set(myExtensions); + if (isCI) { + this._logService.info(`$startExtensionHost: global extensions: ${printExtIds(this._globalRegistry)}`); + this._logService.info(`$startExtensionHost: local extensions: ${printExtIds(this._myRegistry)}`); + } + return this._startExtensionHost(); } @@ -999,6 +1026,11 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme this._globalRegistry.set(globalRegistry.getAllExtensionDescriptions()); this._myRegistry.set(myExtensions); + if (isCI) { + this._logService.info(`$deltaExtensions: global extensions: ${printExtIds(this._globalRegistry)}`); + this._logService.info(`$deltaExtensions: local extensions: ${printExtIds(this._myRegistry)}`); + } + return Promise.resolve(undefined); } @@ -1073,6 +1105,9 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription return event; } +function printExtIds(registry: ExtensionDescriptionRegistry) { + return registry.getAllExtensionDescriptions().map(ext => ext.identifier.value).join(','); +} export const IExtHostExtensionService = createDecorator('IExtHostExtensionService'); diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index d46e67243fc..c1683d51fe8 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -92,10 +92,10 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { )); } - registerProvider(extension: Readonly, provider: vscode.InteractiveEditorSessionProvider): vscode.Disposable { + registerProvider(extension: Readonly, provider: vscode.InteractiveEditorSessionProvider, metadata: vscode.InteractiveEditorSessionProviderMetadata): vscode.Disposable { const wrapper = new ProviderWrapper(extension, provider); this._inputProvider.set(wrapper.handle, wrapper); - this._proxy.$registerInteractiveEditorProvider(wrapper.handle, provider.label, extension.identifier.value, typeof provider.handleInteractiveEditorResponseFeedback === 'function'); + this._proxy.$registerInteractiveEditorProvider(wrapper.handle, metadata.label, extension.identifier.value, typeof provider.handleInteractiveEditorResponseFeedback === 'function'); return toDisposable(() => { this._proxy.$unregisterInteractiveEditorProvider(wrapper.handle); this._inputProvider.delete(wrapper.handle); @@ -143,7 +143,6 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } const apiRequest: vscode.InteractiveEditorRequest = { - session: sessionData.session, prompt: request.prompt, selection: typeConvert.Selection.to(request.selection), wholeRange: typeConvert.Range.to(request.wholeRange), @@ -171,9 +170,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } }; - const task = typeof entry.provider.provideInteractiveEditorResponse2 === 'function' - ? entry.provider.provideInteractiveEditorResponse2(apiRequest, progress, token) - : entry.provider.provideInteractiveEditorResponse(apiRequest, token); + const task = entry.provider.provideInteractiveEditorResponse(sessionData.session, apiRequest, progress, token); Promise.resolve(task).finally(() => done = true); @@ -230,12 +227,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { } $releaseSession(handle: number, sessionId: number) { - const sessionData = this._inputSessions.get(sessionId); - const entry = this._inputProvider.get(handle); - if (sessionData && entry) { - entry.provider.releaseInteractiveEditorSession?.(sessionData.session); - } - this._inputSessions.delete(sessionId); + // TODO@jrieken remove this } private static _isMessageResponse(thing: any): thing is vscode.InteractiveEditorMessageResponse { diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 3de4e43cbf9..51d394e431d 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -47,7 +47,7 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte } -export class ExtHostTreeViews implements ExtHostTreeViewsShape { +export class ExtHostTreeViews extends Disposable implements ExtHostTreeViewsShape { private treeViews: Map> = new Map>(); private treeDragAndDropService: ITreeViewsDnDService = new TreeViewsDnDService(); @@ -57,7 +57,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { private commands: ExtHostCommands, private logService: ILogService ) { - + super(); function isTreeViewConvertableItem(arg: any): boolean { return arg && arg.$treeViewId && (arg.$treeItemHandle || arg.$selectedTreeItems || arg.$focusedTreeItem); } @@ -152,6 +152,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { treeView.dispose(); } }; + this._register(view); return view as vscode.TreeView; } @@ -262,7 +263,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { } private createExtHostTreeView(id: string, options: vscode.TreeViewOptions, extension: IExtensionDescription): ExtHostTreeView { - const treeView = new ExtHostTreeView(id, options, this._proxy, this.commands.converter, this.logService, extension); + const treeView = this._register(new ExtHostTreeView(id, options, this._proxy, this.commands.converter, this.logService, extension)); this.treeViews.set(id, treeView); return treeView; } @@ -359,7 +360,7 @@ class ExtHostTreeView extends Disposable { let refreshingPromise: Promise | null; let promiseCallback: () => void; - this._register(Event.debounce, { message: boolean; elements: (T | Root)[] }>(this._onDidChangeData.event, (result, current) => { + const onDidChangeData = Event.debounce, { message: boolean; elements: (T | Root)[] }>(this._onDidChangeData.event, (result, current) => { if (!result) { result = { message: false, elements: [] }; } @@ -379,7 +380,8 @@ class ExtHostTreeView extends Disposable { result.message = true; } return result; - }, 200, true)(({ message, elements }) => { + }, 200, true); + this._register(onDidChangeData(({ message, elements }) => { if (elements.length) { this.refreshQueue = this.refreshQueue.then(() => { const _promiseCallback = promiseCallback; @@ -801,7 +803,7 @@ class ExtHostTreeView extends Disposable { private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode { this.validateTreeItem(extensionTreeItem); - const disposableStore = new DisposableStore(); + const disposableStore = this._register(new DisposableStore()); const handle = this.createHandle(element, extensionTreeItem, parent); const icon = this.getLightIconPath(extensionTreeItem); const item: ITreeItem = { @@ -970,6 +972,7 @@ class ExtHostTreeView extends Disposable { } override dispose() { + super.dispose(); this._refreshCancellationSource.dispose(); this.clearAll(); diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 561f731654d..2ab734a61a6 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -22,7 +22,7 @@ import { NodeRemoteTunnel } from 'vs/platform/tunnel/node/tunnelService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; +import { CandidatePort, parseAddress } from 'vs/workbench/services/remote/common/tunnelModel'; import * as vscode from 'vscode'; export function getSockets(stdout: string): Record { @@ -349,7 +349,7 @@ export class NodeExtHostTunnelService extends ExtHostTunnelService { const disposeEmitter = new Emitter(); return { - localAddress: t.localAddress, + localAddress: parseAddress(t.localAddress) ?? t.localAddress, remoteAddress: { port: t.tunnelRemotePort, host: t.tunnelRemoteHost }, onDidDispose: disposeEmitter.event, dispose: () => { diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts index ab55eb49740..35e7543d864 100644 --- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts +++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts @@ -62,6 +62,7 @@ import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ensureFileSystemProviderError } from 'vs/platform/files/common/files'; function assertRejects(fn: () => Promise, message: string = 'Expected rejection') { return fn().then(() => assert.ok(false, message), _err => assert.ok(true)); @@ -203,6 +204,8 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync(); }); + ensureFileSystemProviderError(); + // --- workspace symbols test('WorkspaceSymbols, invalid arguments', function () { diff --git a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts index 8ba10f3b7fe..6e8a62e0ce9 100644 --- a/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts +++ b/src/vs/workbench/api/test/browser/extHostConfiguration.test.ts @@ -19,6 +19,7 @@ import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSyste import { FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostConfiguration', function () { @@ -53,6 +54,8 @@ suite('ExtHostConfiguration', function () { }; } + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('getConfiguration fails regression test 1.7.1 -> 1.8 #15552', function () { const extHostConfig = createExtHostConfiguration({ 'search': { @@ -742,7 +745,7 @@ suite('ExtHostConfiguration', function () { } }); const configEventData: IConfigurationChange = { keys: ['farboo.updatedConfig', 'farboo.newConfig'], overrides: [] }; - testObject.onDidChangeConfiguration(e => { + store.add(testObject.onDidChangeConfiguration(e => { assert.deepStrictEqual(testObject.getConfiguration().get('farboo'), { 'config': false, @@ -766,7 +769,7 @@ suite('ExtHostConfiguration', function () { assert.ok(!e.affectsConfiguration('farboo.config', workspaceFolder.uri)); assert.ok(!e.affectsConfiguration('farboo.config', URI.file('any'))); done(); - }); + })); testObject.$acceptConfigurationChanged(newConfigData, configEventData); }); diff --git a/src/vs/workbench/api/test/browser/extHostDecorations.test.ts b/src/vs/workbench/api/test/browser/extHostDecorations.test.ts index 9666ee3e640..26b419f6e06 100644 --- a/src/vs/workbench/api/test/browser/extHostDecorations.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDecorations.test.ts @@ -8,6 +8,7 @@ import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { MainThreadDecorationsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; @@ -20,6 +21,8 @@ suite('ExtHostDecorations', function () { let extHostDecorations: ExtHostDecorations; const providers = new Set(); + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { providers.clear(); @@ -79,6 +82,9 @@ suite('ExtHostDecorations', function () { const secondResult = await Promise.race([second, timeout(30).then(() => false)]); assert.strictEqual(typeof secondResult, 'object'); + + + await timeout(30); }); }); diff --git a/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts b/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts index b6bf3ee4406..6c6de8c7e17 100644 --- a/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDiagnostics.test.ts @@ -18,6 +18,7 @@ import { ExtUri, extUri } from 'vs/base/common/resources'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostDiagnostics', () => { @@ -38,6 +39,8 @@ suite('ExtHostDiagnostics', () => { return undefined; }; + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('disposeCheck', () => { const collection = new DiagnosticCollection('test', 'test', 100, 100, versionProvider, extUri, new DiagnosticsShape(), new Emitter()); @@ -208,7 +211,7 @@ suite('ExtHostDiagnostics', () => { let eventCount = 0; const emitter = new Emitter(); - emitter.event(_ => eventCount += 1); + store.add(emitter.event(_ => eventCount += 1)); const collection = new DiagnosticCollection('test', 'test', 100, 100, versionProvider, extUri, new class extends DiagnosticsShape { override $changeMany() { changeCount += 1; @@ -225,6 +228,7 @@ suite('ExtHostDiagnostics', () => { collection.set(uri, [diag]); assert.strictEqual(changeCount, 2); assert.strictEqual(eventCount, 2); + }); test('diagnostics collection, tuples and undefined (small array), #15585', function () { diff --git a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts index 638cce74296..b8912834f80 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentData.test.ts @@ -13,6 +13,7 @@ import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { mock } from 'vs/base/test/common/mock'; import * as perfData from './extHostDocumentData.test.perf-data'; import { setDefaultGetWordAtTextConfig } from 'vs/editor/common/core/wordHelper'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostDocumentData', () => { @@ -39,6 +40,8 @@ suite('ExtHostDocumentData', () => { ], '\n', 1, 'text', false); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('readonly-ness', () => { assert.throws(() => (data as any).document.uri = null); assert.throws(() => (data as any).document.fileName = 'foofile'); diff --git a/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts index 22e8c4a4a11..3f4255c49c8 100644 --- a/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/extHostDocumentsAndEditors.test.ts @@ -8,6 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { NullLogService } from 'vs/platform/log/common/log'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostDocumentsAndEditors', () => { @@ -17,6 +18,8 @@ suite('ExtHostDocumentsAndEditors', () => { editors = new ExtHostDocumentsAndEditors(new TestRPCProtocol(), new NullLogService()); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('The value of TextDocument.isClosed is incorrect when a text document is closed, #27949', () => { editors.$acceptDocumentsAndEditorsDelta({ @@ -35,7 +38,7 @@ suite('ExtHostDocumentsAndEditors', () => { return new Promise((resolve, reject) => { - editors.onDidRemoveDocuments(e => { + const d = editors.onDidRemoveDocuments(e => { try { for (const data of e) { @@ -44,6 +47,8 @@ suite('ExtHostDocumentsAndEditors', () => { resolve(undefined); } catch (e) { reject(e); + } finally { + d.dispose(); } }); diff --git a/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts b/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts index 39bf2306faa..cedfaa5e426 100644 --- a/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts +++ b/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts @@ -11,6 +11,7 @@ import { IEditorTabDto, IEditorTabGroupDto, MainThreadEditorTabsShape, TabInputK import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TextMergeTabInput, TextTabInput } from 'vs/workbench/api/common/extHostTypes'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostEditorTabs', function () { @@ -28,6 +29,8 @@ suite('ExtHostEditorTabs', function () { return { ...defaultTabDto, ...dto }; } + const store = ensureNoDisposablesAreLeakedInTestSuite(); + test('Ensure empty model throws when accessing active group', function () { const extHostEditorTabs = new ExtHostEditorTabs( SingleProxyRPCProtocol(new class extends mock() { @@ -109,7 +112,7 @@ suite('ExtHostEditorTabs', function () { ); let count = 0; - extHostEditorTabs.tabGroups.onDidChangeTabGroups(() => count++); + store.add(extHostEditorTabs.tabGroups.onDidChangeTabGroups(() => count++)); assert.strictEqual(count, 0); @@ -142,7 +145,7 @@ suite('ExtHostEditorTabs', function () { const group2Data: IEditorTabGroupDto = { ...group1Data, groupId: 13 }; const events: vscode.TabGroupChangeEvent[] = []; - extHostEditorTabs.tabGroups.onDidChangeTabGroups(e => events.push(e)); + store.add(extHostEditorTabs.tabGroups.onDidChangeTabGroups(e => events.push(e))); // OPEN extHostEditorTabs.$acceptEditorTabModel([group1Data]); assert.deepStrictEqual(events, [{ @@ -446,7 +449,8 @@ suite('ExtHostEditorTabs', function () { const tab = extHostEditorTabs.tabGroups.all[0].tabs[0]; - const p = new Promise(resolve => extHostEditorTabs.tabGroups.onDidChangeTabs(resolve)); + + const p = new Promise(resolve => store.add(extHostEditorTabs.tabGroups.onDidChangeTabs(resolve))); extHostEditorTabs.$acceptTabOperation({ groupId: 12, diff --git a/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts b/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts index 64d35e85b79..5063354ee59 100644 --- a/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts +++ b/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts @@ -6,9 +6,12 @@ import * as assert from 'assert'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { NullLogService } from 'vs/platform/log/common/log'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostFileSystemEventService', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('FileSystemWatcher ignore events properties are reversed #26851', function () { const protocol: IMainContext = { @@ -23,11 +26,13 @@ suite('ExtHostFileSystemEventService', () => { assert.strictEqual(watcher1.ignoreChangeEvents, false); assert.strictEqual(watcher1.ignoreCreateEvents, false); assert.strictEqual(watcher1.ignoreDeleteEvents, false); + watcher1.dispose(); const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher(undefined!, undefined!, '**/somethingBoring', true, true, true); assert.strictEqual(watcher2.ignoreChangeEvents, true); assert.strictEqual(watcher2.ignoreCreateEvents, true); assert.strictEqual(watcher2.ignoreDeleteEvents, true); + watcher2.dispose(); }); }); diff --git a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts index dc5f1061385..47646595113 100644 --- a/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts @@ -44,7 +44,7 @@ import { nullExtensionDescription as defaultExtension } from 'vs/workbench/servi import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/browser/smartSelect'; import { mock } from 'vs/base/test/common/mock'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { Progress } from 'vs/platform/progress/common/progress'; import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; @@ -55,6 +55,7 @@ import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeatu import { CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostLanguageFeatures', function () { @@ -62,13 +63,13 @@ suite('ExtHostLanguageFeatures', function () { let model: ITextModel; let extHost: ExtHostLanguageFeatures; let mainThread: MainThreadLanguageFeatures; - let disposables: vscode.Disposable[] = []; + const disposables = new DisposableStore(); let rpcProtocol: TestRPCProtocol; let languageFeaturesService: ILanguageFeaturesService; let originalErrorHandler: (e: any) => any; let instantiationService: TestInstantiationService; - suiteSetup(() => { + setup(() => { model = createTextModel( [ @@ -121,7 +122,7 @@ suite('ExtHostLanguageFeatures', function () { } }); rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); - rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); + rpcProtocol.set(MainContext.MainThreadCommands, disposables.add(inst.createInstance(MainThreadCommands, rpcProtocol))); const diagnostics = new ExtHostDiagnostics(rpcProtocol, new NullLogService(), new class extends mock() { }, extHostDocumentsAndEditors); rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); @@ -133,21 +134,22 @@ suite('ExtHostLanguageFeatures', function () { }); rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost); - mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol)); + mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, disposables.add(inst.createInstance(MainThreadLanguageFeatures, rpcProtocol))); }); - suiteTeardown(() => { + teardown(() => { + disposables.clear(); + setUnexpectedErrorHandler(originalErrorHandler); model.dispose(); mainThread.dispose(); instantiationService.dispose(); - }); - teardown(() => { - disposables = dispose(disposables); return rpcProtocol.sync(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + // --- outline test('DocumentSymbols, register/deregister', async () => { @@ -166,12 +168,12 @@ suite('ExtHostLanguageFeatures', function () { }); test('DocumentSymbols, evil provider', async () => { - disposables.push(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { + disposables.add(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { provideDocumentSymbols(): any { throw new Error('evil document symbol provider'); } })); - disposables.push(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { + disposables.add(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { provideDocumentSymbols(): any { return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))]; } @@ -183,7 +185,7 @@ suite('ExtHostLanguageFeatures', function () { }); test('DocumentSymbols, data conversion', async () => { - disposables.push(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { + disposables.add(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentSymbolProvider { provideDocumentSymbols(): any { return [new types.SymbolInformation('test', types.SymbolKind.Field, new types.Range(0, 0, 0, 0))]; } @@ -207,7 +209,7 @@ suite('ExtHostLanguageFeatures', function () { { name: 'containerPort', range: { startLineNumber: 4, startColumn: 9, endLineNumber: 4, endColumn: 26 } } ]; - extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, { + disposables.add(extHost.registerDocumentSymbolProvider(defaultExtension, defaultSelector, { provideDocumentSymbols: (doc, token): any => { return symbols.map(s => { return new types.SymbolInformation( @@ -217,7 +219,7 @@ suite('ExtHostLanguageFeatures', function () { ); }); } - }); + })); await rpcProtocol.sync(); @@ -231,12 +233,12 @@ suite('ExtHostLanguageFeatures', function () { test('CodeLens, evil provider', async () => { - disposables.push(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { + disposables.add(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { provideCodeLenses(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { + disposables.add(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { provideCodeLenses() { return [new types.CodeLens(new types.Range(0, 0, 0, 0))]; } @@ -245,11 +247,12 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensModel(languageFeaturesService.codeLensProvider, model, CancellationToken.None); assert.strictEqual(value.lenses.length, 1); + value.dispose(); }); test('CodeLens, do not resolve a resolved lens', async () => { - disposables.push(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { + disposables.add(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { provideCodeLenses(): any { return [new types.CodeLens( new types.Range(0, 0, 0, 0), @@ -267,11 +270,12 @@ suite('ExtHostLanguageFeatures', function () { const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.strictEqual(symbol!.command!.id, 'id'); assert.strictEqual(symbol!.command!.title, 'Title'); + value.dispose(); }); test('CodeLens, missing command', async () => { - disposables.push(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { + disposables.add(extHost.registerCodeLensProvider(defaultExtension, defaultSelector, new class implements vscode.CodeLensProvider { provideCodeLenses() { return [new types.CodeLens(new types.Range(0, 0, 0, 0))]; } @@ -284,13 +288,14 @@ suite('ExtHostLanguageFeatures', function () { const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.strictEqual(symbol!.command!.id, 'missing'); assert.strictEqual(symbol!.command!.title, '!!MISSING: command!!'); + value.dispose(); }); // --- definition test('Definition, data conversion', async () => { - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return [new types.Location(model.uri, new types.Range(1, 2, 3, 4))]; } @@ -306,12 +311,12 @@ suite('ExtHostLanguageFeatures', function () { test('Definition, one or many', async () => { - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return [new types.Location(model.uri, new types.Range(1, 1, 1, 1))]; } })); - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return new types.Location(model.uri, new types.Range(2, 1, 1, 1)); } @@ -324,13 +329,13 @@ suite('ExtHostLanguageFeatures', function () { test('Definition, registration order', async () => { - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return [new types.Location(URI.parse('far://first'), new types.Range(2, 3, 4, 5))]; } })); - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return new types.Location(URI.parse('far://second'), new types.Range(1, 2, 3, 4)); } @@ -346,12 +351,12 @@ suite('ExtHostLanguageFeatures', function () { test('Definition, evil provider', async () => { - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { throw new Error('evil provider'); } })); - disposables.push(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { + disposables.add(extHost.registerDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.DefinitionProvider { provideDefinition(): any { return new types.Location(model.uri, new types.Range(1, 1, 1, 1)); } @@ -366,7 +371,7 @@ suite('ExtHostLanguageFeatures', function () { test('Declaration, data conversion', async () => { - disposables.push(extHost.registerDeclarationProvider(defaultExtension, defaultSelector, new class implements vscode.DeclarationProvider { + disposables.add(extHost.registerDeclarationProvider(defaultExtension, defaultSelector, new class implements vscode.DeclarationProvider { provideDeclaration(): any { return [new types.Location(model.uri, new types.Range(1, 2, 3, 4))]; } @@ -384,7 +389,7 @@ suite('ExtHostLanguageFeatures', function () { test('Implementation, data conversion', async () => { - disposables.push(extHost.registerImplementationProvider(defaultExtension, defaultSelector, new class implements vscode.ImplementationProvider { + disposables.add(extHost.registerImplementationProvider(defaultExtension, defaultSelector, new class implements vscode.ImplementationProvider { provideImplementation(): any { return [new types.Location(model.uri, new types.Range(1, 2, 3, 4))]; } @@ -402,7 +407,7 @@ suite('ExtHostLanguageFeatures', function () { test('Type Definition, data conversion', async () => { - disposables.push(extHost.registerTypeDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.TypeDefinitionProvider { + disposables.add(extHost.registerTypeDefinitionProvider(defaultExtension, defaultSelector, new class implements vscode.TypeDefinitionProvider { provideTypeDefinition(): any { return [new types.Location(model.uri, new types.Range(1, 2, 3, 4))]; } @@ -420,7 +425,7 @@ suite('ExtHostLanguageFeatures', function () { test('HoverProvider, word range at pos', async () => { - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { return new types.Hover('Hello'); } @@ -436,7 +441,7 @@ suite('ExtHostLanguageFeatures', function () { test('HoverProvider, given range', async () => { - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { return new types.Hover('Hello', new types.Range(3, 0, 8, 7)); } @@ -451,14 +456,14 @@ suite('ExtHostLanguageFeatures', function () { test('HoverProvider, registration order', async () => { - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { return new types.Hover('registered first'); } })); - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { return new types.Hover('registered second'); } @@ -475,12 +480,12 @@ suite('ExtHostLanguageFeatures', function () { test('HoverProvider, evil provider', async () => { - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { + disposables.add(extHost.registerHoverProvider(defaultExtension, defaultSelector, new class implements vscode.HoverProvider { provideHover(): any { return new types.Hover('Hello'); } @@ -495,7 +500,7 @@ suite('ExtHostLanguageFeatures', function () { test('Occurrences, data conversion', async () => { - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))]; } @@ -511,12 +516,12 @@ suite('ExtHostLanguageFeatures', function () { test('Occurrences, order 1/2', async () => { - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return []; } })); - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))]; } @@ -532,12 +537,12 @@ suite('ExtHostLanguageFeatures', function () { test('Occurrences, order 2/2', async () => { - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return [new types.DocumentHighlight(new types.Range(0, 0, 0, 2))]; } })); - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, '*', new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))]; } @@ -553,13 +558,13 @@ suite('ExtHostLanguageFeatures', function () { test('Occurrences, evil provider', async () => { - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { + disposables.add(extHost.registerDocumentHighlightProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentHighlightProvider { provideDocumentHighlights(): any { return [new types.DocumentHighlight(new types.Range(0, 0, 0, 4))]; } @@ -574,13 +579,13 @@ suite('ExtHostLanguageFeatures', function () { test('References, registration order', async () => { - disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { + disposables.add(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { return [new types.Location(URI.parse('far://register/first'), new types.Range(0, 0, 0, 0))]; } })); - disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { + disposables.add(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { return [new types.Location(URI.parse('far://register/second'), new types.Range(0, 0, 0, 0))]; } @@ -596,7 +601,7 @@ suite('ExtHostLanguageFeatures', function () { test('References, data conversion', async () => { - disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { + disposables.add(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { return [new types.Location(model.uri, new types.Position(0, 0))]; } @@ -612,12 +617,12 @@ suite('ExtHostLanguageFeatures', function () { test('References, evil provider', async () => { - disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { + disposables.add(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { + disposables.add(extHost.registerReferenceProvider(defaultExtension, defaultSelector, new class implements vscode.ReferenceProvider { provideReferences(): any { return [new types.Location(model.uri, new types.Range(0, 0, 0, 0))]; } @@ -632,7 +637,7 @@ suite('ExtHostLanguageFeatures', function () { test('Quick Fix, command data conversion', async () => { - disposables.push(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, { + disposables.add(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, { provideCodeActions(): vscode.Command[] { return [ { command: 'test1', title: 'Testing1' }, @@ -642,18 +647,20 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.QuickFix }, Progress.None, CancellationToken.None); + const value = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.QuickFix }, Progress.None, CancellationToken.None); + const { validActions: actions } = value; assert.strictEqual(actions.length, 2); const [first, second] = actions; assert.strictEqual(first.action.title, 'Testing1'); assert.strictEqual(first.action.command!.id, 'test1'); assert.strictEqual(second.action.title, 'Testing2'); assert.strictEqual(second.action.command!.id, 'test2'); + value.dispose(); }); test('Quick Fix, code action data conversion', async () => { - disposables.push(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, { + disposables.add(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, { provideCodeActions(): vscode.CodeAction[] { return [ { @@ -666,19 +673,21 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None); + const value = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None); + const { validActions: actions } = value; assert.strictEqual(actions.length, 1); const [first] = actions; assert.strictEqual(first.action.title, 'Testing1'); assert.strictEqual(first.action.command!.title, 'Testing1Command'); assert.strictEqual(first.action.command!.id, 'test1'); assert.strictEqual(first.action.kind, 'test.scope'); + value.dispose(); }); test('Cannot read property \'id\' of undefined, #29469', async () => { - disposables.push(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { + disposables.add(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { provideCodeActions(): any { return [ undefined, @@ -689,39 +698,43 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None); + const value = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None); + const { validActions: actions } = value; assert.strictEqual(actions.length, 1); + value.dispose(); }); test('Quick Fix, evil provider', async () => { - disposables.push(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { + disposables.add(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { provideCodeActions(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { + disposables.add(extHost.registerCodeActionProvider(defaultExtension, defaultSelector, new class implements vscode.CodeActionProvider { provideCodeActions(): any { return [{ command: 'test', title: 'Testing' }]; } })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.QuickFix }, Progress.None, CancellationToken.None); + const value = await getCodeActions(languageFeaturesService.codeActionProvider, model, model.getFullModelRange(), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.QuickFix }, Progress.None, CancellationToken.None); + const { validActions: actions } = value; assert.strictEqual(actions.length, 1); + value.dispose(); }); // --- navigate types test('Navigate types, evil provider', async () => { - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { throw new Error('evil'); } })); - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { return [new types.SymbolInformation('testing', types.SymbolKind.Array, new types.Range(0, 0, 1, 1))]; } @@ -736,19 +749,19 @@ suite('ExtHostLanguageFeatures', function () { test('Navigate types, de-duplicate results', async () => { const uri = URI.from({ scheme: 'foo', path: '/some/path' }); - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; } })); - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; // get de-duped } })); - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { return [new types.SymbolInformation('ONE', types.SymbolKind.Array, undefined, new types.Location(uri, undefined!))]; // NO dedupe because of resolve } @@ -757,7 +770,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - disposables.push(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { + disposables.add(extHost.registerWorkspaceSymbolProvider(defaultExtension, new class implements vscode.WorkspaceSymbolProvider { provideWorkspaceSymbols(): any { return [new types.SymbolInformation('ONE', types.SymbolKind.Struct, undefined, new types.Location(uri, new types.Range(0, 0, 1, 1)))]; // NO dedupe because of kind } @@ -772,7 +785,7 @@ suite('ExtHostLanguageFeatures', function () { test('Rename, evil provider 0/2', async () => { - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { provideRenameEdits(): any { throw new class Foo { }; } @@ -790,7 +803,7 @@ suite('ExtHostLanguageFeatures', function () { test('Rename, evil provider 1/2', async () => { - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { provideRenameEdits(): any { throw Error('evil'); } @@ -803,13 +816,13 @@ suite('ExtHostLanguageFeatures', function () { test('Rename, evil provider 2/2', async () => { - disposables.push(extHost.registerRenameProvider(defaultExtension, '*', new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, '*', new class implements vscode.RenameProvider { provideRenameEdits(): any { throw Error('evil'); } })); - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { provideRenameEdits(): any { const edit = new types.WorkspaceEdit(); edit.replace(model.uri, new types.Range(0, 0, 0, 0), 'testing'); @@ -824,7 +837,7 @@ suite('ExtHostLanguageFeatures', function () { test('Rename, ordering', async () => { - disposables.push(extHost.registerRenameProvider(defaultExtension, '*', new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, '*', new class implements vscode.RenameProvider { provideRenameEdits(): any { const edit = new types.WorkspaceEdit(); edit.replace(model.uri, new types.Range(0, 0, 0, 0), 'testing'); @@ -833,7 +846,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { provideRenameEdits(): any { return; } @@ -849,7 +862,7 @@ suite('ExtHostLanguageFeatures', function () { const called = [false, false, false, false]; - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { prepareRename(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { called[0] = true; const range = document.getWordRangeAtPosition(position); @@ -862,7 +875,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { prepareRename(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { called[2] = true; return Promise.reject('Cannot rename this symbol2.'); @@ -883,7 +896,7 @@ suite('ExtHostLanguageFeatures', function () { const called = [false, false, false]; - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { prepareRename(document: vscode.TextDocument, position: vscode.Position,): vscode.ProviderResult { called[0] = true; const range = document.getWordRangeAtPosition(position); @@ -896,7 +909,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - disposables.push(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { + disposables.add(extHost.registerRenameProvider(defaultExtension, defaultSelector, new class implements vscode.RenameProvider { provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string,): vscode.ProviderResult { called[2] = true; @@ -915,13 +928,13 @@ suite('ExtHostLanguageFeatures', function () { test('Parameter Hints, order', async () => { - disposables.push(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { + disposables.add(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { provideSignatureHelp(): any { return undefined; } }, [])); - disposables.push(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { + disposables.add(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { provideSignatureHelp(): vscode.SignatureHelp { return { signatures: [], @@ -938,7 +951,7 @@ suite('ExtHostLanguageFeatures', function () { test('Parameter Hints, evil provider', async () => { - disposables.push(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { + disposables.add(extHost.registerSignatureHelpProvider(defaultExtension, defaultSelector, new class implements vscode.SignatureHelpProvider { provideSignatureHelp(): any { throw new Error('evil'); } @@ -953,74 +966,77 @@ suite('ExtHostLanguageFeatures', function () { test('Suggest, order 1/3', async () => { - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, '*', new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, '*', new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('testing1')]; } }, [])); - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('testing2')]; } }, [])); await rpcProtocol.sync(); - const { items } = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); - assert.strictEqual(items.length, 1); - assert.strictEqual(items[0].completion.insertText, 'testing2'); + const value = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); + assert.strictEqual(value.items.length, 1); + assert.strictEqual(value.items[0].completion.insertText, 'testing2'); + value.disposable.dispose(); }); test('Suggest, order 2/3', async () => { - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, '*', new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, '*', new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('weak-selector')]; // weaker selector but result } }, [])); - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return []; // stronger selector but not a good result; } }, [])); await rpcProtocol.sync(); - const { items } = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); - assert.strictEqual(items.length, 1); - assert.strictEqual(items[0].completion.insertText, 'weak-selector'); + const value = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); + assert.strictEqual(value.items.length, 1); + assert.strictEqual(value.items[0].completion.insertText, 'weak-selector'); + value.disposable.dispose(); }); test('Suggest, order 3/3', async () => { - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('strong-1')]; } }, [])); - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('strong-2')]; } }, [])); await rpcProtocol.sync(); - const { items } = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); - assert.strictEqual(items.length, 2); - assert.strictEqual(items[0].completion.insertText, 'strong-1'); // sort by label - assert.strictEqual(items[1].completion.insertText, 'strong-2'); + const value = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); + assert.strictEqual(value.items.length, 2); + assert.strictEqual(value.items[0].completion.insertText, 'strong-1'); // sort by label + assert.strictEqual(value.items[1].completion.insertText, 'strong-2'); + value.disposable.dispose(); }); test('Suggest, evil provider', async () => { - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { throw new Error('evil'); } }, [])); - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return [new types.CompletionItem('testing')]; } @@ -1028,13 +1044,15 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); - const { items } = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); - assert.strictEqual(items[0].container.incomplete, false); + const value = await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))); + assert.strictEqual(value.items[0].container.incomplete, false); + value.disposable.dispose(); + }); test('Suggest, CompletionList', async () => { - disposables.push(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { + disposables.add(extHost.registerCompletionItemProvider(defaultExtension, defaultSelector, new class implements vscode.CompletionItemProvider { provideCompletionItems(): any { return new types.CompletionList([new types.CompletionItem('hello')], true); } @@ -1043,6 +1061,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); await provideSuggestionItems(languageFeaturesService.completionProvider, model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(languages.CompletionItemKind.Snippet))).then(model => { assert.strictEqual(model.items[0].container.incomplete, true); + model.disposable.dispose(); }); }); @@ -1055,7 +1074,7 @@ suite('ExtHostLanguageFeatures', function () { }; test('Format Doc, data conversion', async () => { - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'testing'), types.TextEdit.setEndOfLine(types.EndOfLine.LF)]; } @@ -1073,7 +1092,7 @@ suite('ExtHostLanguageFeatures', function () { }); test('Format Doc, evil provider', async () => { - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { throw new Error('evil'); } @@ -1085,19 +1104,19 @@ suite('ExtHostLanguageFeatures', function () { test('Format Doc, order', async () => { - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { return undefined; } })); - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'testing')]; } })); - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { return undefined; } @@ -1112,7 +1131,7 @@ suite('ExtHostLanguageFeatures', function () { }); test('Format Range, data conversion', async () => { - disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { + disposables.add(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { provideDocumentRangeFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'testing')]; } @@ -1127,17 +1146,17 @@ suite('ExtHostLanguageFeatures', function () { }); test('Format Range, + format_doc', async () => { - disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { + disposables.add(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { provideDocumentRangeFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 0, 0), 'range')]; } })); - disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { + disposables.add(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { provideDocumentRangeFormattingEdits(): any { return [new types.TextEdit(new types.Range(2, 3, 4, 5), 'range2')]; } })); - disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { + disposables.add(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 1, 1), 'doc')]; } @@ -1154,7 +1173,7 @@ suite('ExtHostLanguageFeatures', function () { }); test('Format Range, evil provider', async () => { - disposables.push(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { + disposables.add(extHost.registerDocumentRangeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentRangeFormattingEditProvider { provideDocumentRangeFormattingEdits(): any { throw new Error('evil'); } @@ -1166,7 +1185,7 @@ suite('ExtHostLanguageFeatures', function () { test('Format on Type, data conversion', async () => { - disposables.push(extHost.registerOnTypeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.OnTypeFormattingEditProvider { + disposables.add(extHost.registerOnTypeFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.OnTypeFormattingEditProvider { provideOnTypeFormattingEdits(): any { return [new types.TextEdit(new types.Range(0, 0, 0, 0), arguments[2])]; } @@ -1182,7 +1201,7 @@ suite('ExtHostLanguageFeatures', function () { test('Links, data conversion', async () => { - disposables.push(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { + disposables.add(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { provideDocumentLinks() { const link = new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3')); link.tooltip = 'tooltip'; @@ -1191,7 +1210,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { links } = await getLinks(languageFeaturesService.linkProvider, model, CancellationToken.None); + const { links } = disposables.add(await getLinks(languageFeaturesService.linkProvider, model, CancellationToken.None)); assert.strictEqual(links.length, 1); const [first] = links; assert.strictEqual(first.url?.toString(), 'foo:bar#3'); @@ -1201,20 +1220,20 @@ suite('ExtHostLanguageFeatures', function () { test('Links, evil provider', async () => { - disposables.push(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { + disposables.add(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { provideDocumentLinks() { return [new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3'))]; } })); - disposables.push(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { + disposables.add(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { provideDocumentLinks(): any { throw new Error(); } })); await rpcProtocol.sync(); - const { links } = await getLinks(languageFeaturesService.linkProvider, model, CancellationToken.None); + const { links } = disposables.add(await getLinks(languageFeaturesService.linkProvider, model, CancellationToken.None)); assert.strictEqual(links.length, 1); const [first] = links; assert.strictEqual(first.url?.toString(), 'foo:bar#3'); @@ -1223,7 +1242,7 @@ suite('ExtHostLanguageFeatures', function () { test('Document colors, data conversion', async () => { - disposables.push(extHost.registerColorProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentColorProvider { + disposables.add(extHost.registerColorProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentColorProvider { provideDocumentColors(): vscode.ColorInformation[] { return [new types.ColorInformation(new types.Range(0, 0, 0, 20), new types.Color(0.1, 0.2, 0.3, 0.4))]; } @@ -1243,7 +1262,7 @@ suite('ExtHostLanguageFeatures', function () { // -- selection ranges test('Selection Ranges, data conversion', async () => { - disposables.push(extHost.registerSelectionRangeProvider(defaultExtension, defaultSelector, new class implements vscode.SelectionRangeProvider { + disposables.add(extHost.registerSelectionRangeProvider(defaultExtension, defaultSelector, new class implements vscode.SelectionRangeProvider { provideSelectionRanges() { return [ new types.SelectionRange(new types.Range(0, 10, 0, 18), new types.SelectionRange(new types.Range(0, 2, 0, 20))), diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index df4903ee04a..3f780a03299 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -8,12 +8,9 @@ import * as sinon from 'sinon'; import { Emitter } from 'vs/base/common/event'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainThreadTreeViewsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTreeViewsShape, MainContext, MainThreadCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; import { TreeDataProvider, TreeItem } from 'vscode'; import { TestRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { MainThreadCommands } from 'vs/workbench/api/browser/mainThreadCommands'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mock } from 'vs/base/test/common/mock'; import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -21,8 +18,10 @@ import type { IDisposable } from 'vs/base/common/lifecycle'; import { nullExtensionDescription as extensionsDescription } from 'vs/workbench/services/extensions/common/extensions'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtHostTreeView', function () { + const store = ensureNoDisposablesAreLeakedInTestSuite(); class RecordingShape extends mock() { @@ -41,6 +40,10 @@ suite('ExtHostTreeView', function () { return Promise.resolve(); } + override $disposeTree(treeViewId: string): Promise { + return Promise.resolve(); + } + } let testObject: ExtHostTreeViews; @@ -50,7 +53,6 @@ suite('ExtHostTreeView', function () { let tree: { [key: string]: any }; let labels: { [key: string]: string }; let nodes: { [key: string]: { key: string } }; - let instantiationService: TestInstantiationService; setup(() => { tree = { @@ -68,16 +70,12 @@ suite('ExtHostTreeView', function () { nodes = {}; const rpcProtocol = new TestRPCProtocol(); - // Use IInstantiationService to get typechecking when instantiating - let inst: IInstantiationService; - { - instantiationService = new TestInstantiationService(); - inst = instantiationService; - } - rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); + rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock() { + override $registerCommand() { } + }); target = new RecordingShape(); - testObject = new ExtHostTreeViews(target, new ExtHostCommands( + testObject = store.add(new ExtHostTreeViews(target, new ExtHostCommands( rpcProtocol, new NullLogService(), new class extends mock() { @@ -85,7 +83,7 @@ suite('ExtHostTreeView', function () { return true; } } - ), new NullLogService()); + ), new NullLogService())); onDidChangeTreeNode = new Emitter<{ key: string } | undefined>(); onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription); @@ -95,10 +93,6 @@ suite('ExtHostTreeView', function () { return loadCompleteTree('testNodeTreeProvider'); }); - teardown(() => { - instantiationService.dispose(); - }); - test('construct node tree', () => { return testObject.$getChildren('testNodeTreeProvider') .then(elements => { @@ -209,7 +203,7 @@ suite('ExtHostTreeView', function () { 'ba': {} }; let caughtExpectedError = false; - target.onRefresh.event(() => { + store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { const actuals = elements?.map(e => e.handle); @@ -220,21 +214,21 @@ suite('ExtHostTreeView', function () { .catch(() => caughtExpectedError = true) .finally(() => caughtExpectedError ? done() : assert.fail('Expected duplicate id error not thrown.')); }); - }); + })); onDidChangeTreeNode.fire(undefined); }); test('refresh root', function (done) { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.strictEqual(undefined, actuals); done(); - }); + })); onDidChangeTreeNode.fire(undefined); }); test('refresh a parent node', () => { return new Promise((c, e) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:b'], Object.keys(actuals)); assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', @@ -242,13 +236,13 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.Collapsed }); c(undefined); - }); + })); onDidChangeTreeNode.fire(getNode('b')); }); }); test('refresh a leaf node', function (done) { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:b/0:bb'], Object.keys(actuals)); assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b/0:bb']), { handle: '0/0:b/0:bb', @@ -257,7 +251,7 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.None }); done(); - }); + })); onDidChangeTreeNode.fire(getNode('bb')); }); @@ -277,7 +271,7 @@ suite('ExtHostTreeView', function () { test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals)); assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', @@ -291,7 +285,7 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.None }); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('b')); onDidChangeTreeNode.fire(getNode('aa')); onDidChangeTreeNode.fire(getNode('bb')); @@ -300,7 +294,7 @@ suite('ExtHostTreeView', function () { test('refresh parent and child node trigger refresh only on parent - scenario 2', async () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals)); assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', @@ -314,7 +308,7 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.None }); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('bb')); onDidChangeTreeNode.fire(getNode('aa')); onDidChangeTreeNode.fire(getNode('b')); @@ -323,7 +317,7 @@ suite('ExtHostTreeView', function () { test('refresh an element for label change', function (done) { labels['a'] = 'aa'; - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:a'], Object.keys(actuals)); assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a']), { handle: '0/0:aa', @@ -331,16 +325,16 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.Collapsed }); done(); - }); + })); onDidChangeTreeNode.fire(getNode('a')); }); test('refresh calls are throttled on roots', () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.strictEqual(undefined, actuals); resolve(); - }); + })); onDidChangeTreeNode.fire(undefined); onDidChangeTreeNode.fire(undefined); onDidChangeTreeNode.fire(undefined); @@ -350,10 +344,10 @@ suite('ExtHostTreeView', function () { test('refresh calls are throttled on elements', () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('a')); onDidChangeTreeNode.fire(getNode('b')); @@ -364,10 +358,10 @@ suite('ExtHostTreeView', function () { test('refresh calls are throttled on unknown elements', () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('a')); onDidChangeTreeNode.fire(getNode('b')); @@ -378,10 +372,10 @@ suite('ExtHostTreeView', function () { test('refresh calls are throttled on unknown elements and root', () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.strictEqual(undefined, actuals); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('a')); onDidChangeTreeNode.fire(getNode('b')); @@ -392,10 +386,10 @@ suite('ExtHostTreeView', function () { test('refresh calls are throttled on elements and root', () => { return runWithEventMerging((resolve) => { - target.onRefresh.event(actuals => { + store.add(target.onRefresh.event(actuals => { assert.strictEqual(undefined, actuals); resolve(); - }); + })); onDidChangeTreeNode.fire(getNode('a')); onDidChangeTreeNode.fire(getNode('b')); @@ -409,13 +403,13 @@ suite('ExtHostTreeView', function () { 'a/0:b': {} }; - target.onRefresh.event(() => { + store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:a//0:b']); done(); }); - }); + })); onDidChangeTreeNode.fire(undefined); }); @@ -451,7 +445,7 @@ suite('ExtHostTreeView', function () { tree['f'] = {}; tree[dupItems['adup2']] = {}; - target.onRefresh.event(() => { + store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { const actuals = elements?.map(e => e.handle); @@ -463,7 +457,7 @@ suite('ExtHostTreeView', function () { done(); }); }); - }); + })); onDidChangeTreeNode.fire(undefined); }); @@ -473,13 +467,13 @@ suite('ExtHostTreeView', function () { 'c': {} }; - target.onRefresh.event(() => { + store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:c']); done(); }); - }); + })); onDidChangeTreeNode.fire(undefined); }); diff --git a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts index 14ef427ac59..3d66d2dffa6 100644 --- a/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts @@ -31,6 +31,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('MainThreadDocumentsAndEditors', () => { @@ -120,10 +121,12 @@ suite('MainThreadDocumentsAndEditors', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('Model#add', () => { deltas.length = 0; - modelService.createModel('farboo', null); + disposables.add(modelService.createModel('farboo', null)); assert.strictEqual(deltas.length, 1); const [delta] = deltas; @@ -143,6 +146,7 @@ suite('MainThreadDocumentsAndEditors', () => { TextModel._MODEL_SYNC_LIMIT = largeModelString.length / 2; const model = modelService.createModel(largeModelString, null); + disposables.add(model); assert.ok(model.isTooLargeForSyncing()); assert.strictEqual(deltas.length, 1); @@ -172,6 +176,7 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; assert.strictEqual(deltas.length, 0); editor.dispose(); + model.dispose(); } finally { TextModel._MODEL_SYNC_LIMIT = oldLimit; @@ -182,6 +187,7 @@ suite('MainThreadDocumentsAndEditors', () => { this.timeout(1000 * 60); // increase timeout for this one test const model = modelService.createModel('test', null, undefined, true); + disposables.add(model); assert.ok(model.isForSimpleWidget); assert.strictEqual(deltas.length, 1); @@ -227,10 +233,10 @@ suite('MainThreadDocumentsAndEditors', () => { assert.strictEqual(second.newActiveEditor, undefined); editor.dispose(); + model.dispose(); }); test('editor with dispos-ed/-ing model', () => { - modelService.createModel('foobar', null); const model = modelService.createModel('farboo', null); const editor = myCreateTestCodeEditor(model); @@ -248,5 +254,6 @@ suite('MainThreadDocumentsAndEditors', () => { assert.strictEqual(first.addedEditors, undefined); editor.dispose(); + model.dispose(); }); }); diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts index 7a9bf86bce9..71dd40df6d5 100644 --- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts @@ -4,57 +4,58 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -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 } from 'vs/workbench/api/common/extHost.protocol'; -import { mock } from 'vs/base/test/common/mock'; import { Event } from 'vs/base/common/event'; +import { DisposableStore, IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { Range } from 'vs/editor/common/core/range'; -import { Position } from 'vs/editor/common/core/position'; -import { IModelService } from 'vs/editor/common/services/model'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestLifecycleService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { BulkEditService } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditService'; -import { NullLogService, ILogService } from 'vs/platform/log/common/log'; -import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IReference, ImmortalReference, DisposableStore } from 'vs/base/common/lifecycle'; -import { LabelService } from 'vs/workbench/services/label/common/labelService'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +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 { ITextSnapshot } from 'vs/editor/common/model'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { LanguageService } from 'vs/editor/common/services/languageService'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ModelService } from 'vs/editor/common/services/modelService'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; +import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +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 { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILabelService } from 'vs/platform/label/common/label'; +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 { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { MainThreadBulkEdits } from 'vs/workbench/api/browser/mainThreadBulkEdits'; +import { IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; +import { BulkEditService } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IWorkingCopyFileService, IMoveOperation, IDeleteOperation, ICopyOperation, ICreateFileOperation, ICreateOperation } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; -import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { TestTextResourcePropertiesService, TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ITextSnapshot } from 'vs/editor/common/model'; +import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { LanguageService } from 'vs/editor/common/services/languageService'; -import { MainThreadBulkEdits } from 'vs/workbench/api/browser/mainThreadBulkEdits'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ICopyOperation, ICreateFileOperation, ICreateOperation, IDeleteOperation, IMoveOperation, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestFileService, TestLifecycleService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; suite('MainThreadEditors', () => { @@ -185,9 +186,11 @@ suite('MainThreadEditors', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test(`applyWorkspaceEdit returns false if model is changed by user`, () => { - const model = modelService.createModel('something', null, resource); + const model = disposables.add(modelService.createModel('something', null, resource)); const workspaceResourceEdit: IWorkspaceTextEditDto = { resource: resource, @@ -208,7 +211,7 @@ suite('MainThreadEditors', () => { test(`issue #54773: applyWorkspaceEdit checks model version in race situation`, () => { - const model = modelService.createModel('something', null, resource); + const model = disposables.add(modelService.createModel('something', null, resource)); const workspaceResourceEdit1: IWorkspaceTextEditDto = { resource: resource, diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index e226162d5f5..a070305634e 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -10,10 +10,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomEmitter } from 'vs/base/browser/event'; import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; -import { IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, dispose, DisposableStore, setDisposableTracker, DisposableTracker, DisposableInfo } from 'vs/base/common/lifecycle'; import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -36,6 +36,8 @@ import { windowLogId } from 'vs/workbench/services/log/common/logConstants'; import { ByteSize } from 'vs/platform/files/common/files'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import product from 'vs/platform/product/common/product'; class InspectContextKeysAction extends Action2 { @@ -506,12 +508,104 @@ class RemoveLargeStorageEntriesAction extends Action2 { } } +let tracker: DisposableTracker | undefined = undefined; +let trackedDisposables = new Set(); + +const DisposablesSnapshotStateContext = new RawContextKey<'started' | 'pending' | 'stopped'>('dirtyWorkingCopies', 'stopped'); + +class StartTrackDisposables extends Action2 { + + constructor() { + super({ + id: 'workbench.action.startTrackDisposables', + title: { value: localize('startTrackDisposables', "Start Tracking Disposables"), original: 'Start Tracking Disposables' }, + category: Categories.Developer, + f1: true, + precondition: ContextKeyExpr.and(DisposablesSnapshotStateContext.isEqualTo('pending').negate(), DisposablesSnapshotStateContext.isEqualTo('started').negate()) + }); + } + + run(accessor: ServicesAccessor): void { + const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService)); + disposablesSnapshotStateContext.set('started'); + + trackedDisposables.clear(); + + tracker = new DisposableTracker(); + setDisposableTracker(tracker); + } +} + +class SnapshotTrackedDisposables extends Action2 { + + constructor() { + super({ + id: 'workbench.action.snapshotTrackedDisposables', + title: { value: localize('snapshotTrackedDisposables', "Snapshot Tracked Disposables"), original: 'Snapshot Tracked Disposables' }, + category: Categories.Developer, + f1: true, + precondition: DisposablesSnapshotStateContext.isEqualTo('started') + }); + } + + run(accessor: ServicesAccessor): void { + const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService)); + disposablesSnapshotStateContext.set('pending'); + + trackedDisposables = new Set(tracker?.computeLeakingDisposables(1000)?.leaks.map(disposable => disposable.value)); + } +} + +class StopTrackDisposables extends Action2 { + + constructor() { + super({ + id: 'workbench.action.stopTrackDisposables', + title: { value: localize('stopTrackDisposables', "Stop Tracking Disposables"), original: 'Stop Tracking Disposables' }, + category: Categories.Developer, + f1: true, + precondition: DisposablesSnapshotStateContext.isEqualTo('pending') + }); + } + + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + + const disposablesSnapshotStateContext = DisposablesSnapshotStateContext.bindTo(accessor.get(IContextKeyService)); + disposablesSnapshotStateContext.set('stopped'); + + if (tracker) { + const disposableLeaks = new Set(); + + for (const disposable of new Set(tracker.computeLeakingDisposables(1000)?.leaks) ?? []) { + if (trackedDisposables.has(disposable.value)) { + disposableLeaks.add(disposable); + } + } + + const leaks = tracker.computeLeakingDisposables(1000, Array.from(disposableLeaks)); + if (leaks) { + editorService.openEditor({ resource: undefined, contents: leaks.details }); + } + } + + setDisposableTracker(null); + tracker = undefined; + trackedDisposables.clear(); + } +} + // --- Actions Registration registerAction2(InspectContextKeysAction); registerAction2(ToggleScreencastModeAction); registerAction2(LogStorageAction); registerAction2(LogWorkingCopiesAction); registerAction2(RemoveLargeStorageEntriesAction); +if (!product.commit) { + registerAction2(StartTrackDisposables); + registerAction2(SnapshotTrackedDisposables); + registerAction2(StopTrackDisposables); +} // --- Configuration diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index b501e78d5ea..6e8d0459e6c 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -14,6 +14,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { inQuickPickContext, defaultQuickAccessContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { AnythingQuickAccessProviderRunOptions } from 'vs/platform/quickinput/common/quickAccess'; +import { Codicon } from 'vs/base/common/codicons'; //#region Quick access management commands and keys @@ -155,8 +156,9 @@ registerAction2(class QuickAccessAction extends Action2 { super({ id: 'workbench.action.quickOpenWithModes', title: localize('quickOpenWithModes', "Quick Open"), + icon: Codicon.search, menu: { - id: MenuId.CommandCenter, + id: MenuId.CommandCenterCenter, order: 100 } }); diff --git a/src/vs/workbench/browser/iframe.ts b/src/vs/workbench/browser/iframe.ts deleted file mode 100644 index cd6bfa2be82..00000000000 --- a/src/vs/workbench/browser/iframe.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/** - * Returns a sha-256 composed of `parentOrigin` and `salt` converted to base 32 - */ -export async function parentOriginHash(parentOrigin: string, salt: string): Promise { - // This same code is also inlined at `src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html` - if (!crypto.subtle) { - throw new Error(`'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).`); - } - - const strData = JSON.stringify({ parentOrigin, salt }); - const encoder = new TextEncoder(); - const arrData = encoder.encode(strData); - const hash = await crypto.subtle.digest('sha-256', arrData); - return sha256AsBase32(hash); -} - -function sha256AsBase32(bytes: ArrayBuffer): string { - const array = Array.from(new Uint8Array(bytes)); - const hexArray = array.map(b => b.toString(16).padStart(2, '0')).join(''); - // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 - return BigInt(`0x${hexArray}`).toString(32).padStart(52, '0'); -} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 79d341b32d0..86b9a9721b0 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -39,6 +39,7 @@ import { IOutline } from 'vs/workbench/services/outline/browser/outline'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Emitter } from 'vs/base/common/event'; class OutlineItem extends BreadcrumbsItem { @@ -501,6 +502,59 @@ export class BreadcrumbsControl { } } +export class BreadcrumbsControlFactory { + + private readonly _disposables = new DisposableStore(); + + private _control: BreadcrumbsControl | undefined; + get control() { return this._control; } + + private readonly _onDidEnablementChange = this._disposables.add(new Emitter()); + get onDidEnablementChange() { return this._onDidEnablementChange.event; } + + constructor( + container: HTMLElement, + editorGroup: IEditorGroupView, + options: IBreadcrumbsControlOptions, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IFileService fileService: IFileService + ) { + const config = this._disposables.add(BreadcrumbsConfig.IsEnabled.bindTo(configurationService)); + this._disposables.add(config.onDidChange(() => { + const value = config.getValue(); + if (!value && this._control) { + this._control.dispose(); + this._control = undefined; + this._onDidEnablementChange.fire(); + } else if (value && !this._control) { + this._control = instantiationService.createInstance(BreadcrumbsControl, container, options, editorGroup); + this._control.update(); + this._onDidEnablementChange.fire(); + } + })); + + if (config.getValue()) { + this._control = instantiationService.createInstance(BreadcrumbsControl, container, options, editorGroup); + } + + this._disposables.add(fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (this._control?.model && this._control.model.resource.scheme !== e.scheme) { + // ignore if the scheme of the breadcrumbs resource is not affected + return; + } + if (this._control?.update()) { + this._onDidEnablementChange.fire(); + } + })); + } + + dispose(): void { + this._disposables.dispose(); + this._control?.dispose(); + } +} + //#region commands // toggle command diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 36f68774d5f..42870c2ccdc 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -66,7 +66,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { AccessibilityStatus } from 'vs/workbench/browser/parts/editor/accessibilityStatus'; import { ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; //#region Editor Registrations @@ -126,7 +125,6 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorAutoSave, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AccessibilityStatus, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(UntitledTextEditorWorkingCopyEditorHandler, LifecyclePhase.Ready); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DynamicEditorConfigurations, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index c8cdf695fcb..8764654ad5a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -118,7 +118,7 @@ export interface IEditorGroupTitleHeight { /** * The height offset to e.g. use when drawing drop overlays. * This number may be smaller than `height` if the title control - * decides to have an `offset` that is within the title area + * decides to have an `offset` that is within the title control * (e.g. when breadcrumbs are enabled). */ offset: number; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 01b92a4b7e2..f013b140ff4 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -11,7 +11,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor, IDomNodePagePosition } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -19,7 +19,6 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme'; import { ICloseEditorsFilter, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorPanes } from 'vs/workbench/browser/parts/editor/editorPanes'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; @@ -29,12 +28,10 @@ import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions } from 'vs/workbench/browser/parts/editor/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction } from 'vs/base/common/actions'; -import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -55,6 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { EditorGroupWatermark } from 'vs/workbench/browser/parts/editor/editorGroupWatermark'; +import { EditorTitleControl } from 'vs/workbench/browser/parts/editor/editorTitleControl'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -118,7 +116,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly scopedInstantiationService: IInstantiationService; private readonly titleContainer: HTMLElement; - private titleAreaControl: TitleControl; + private readonly titleControl: EditorTitleControl; private readonly progressBar: ProgressBar; @@ -200,7 +198,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.appendChild(this.titleContainer); // Title control - this.titleAreaControl = this.createTitleAreaControl(); + this.titleControl = this._register(this.scopedInstantiationService.createInstance(EditorTitleControl, this.titleContainer, this.accessor, this)); // Editor container this.editorContainer = document.createElement('div'); @@ -458,24 +456,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleContainer.classList.toggle('show-file-icons', this.accessor.partOptions.showIcons); } - private createTitleAreaControl(): TitleControl { - - // Clear old if existing - if (this.titleAreaControl) { - this.titleAreaControl.dispose(); - clearNode(this.titleContainer); - } - - // Create new based on options - if (this.accessor.partOptions.showTabs) { - this.titleAreaControl = this.scopedInstantiationService.createInstance(TabsTitleControl, this.titleContainer, this.accessor, this); - } else { - this.titleAreaControl = this.scopedInstantiationService.createInstance(NoTabsTitleControl, this.titleContainer, this.accessor, this); - } - - return this.titleAreaControl; - } - private restoreEditors(from: IEditorGroupView | ISerializedEditorGroupModel | null): Promise | undefined { if (this.count === 0) { return; // nothing to show @@ -702,26 +682,21 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title container this.updateTitleContainer(); + // Title control + this.titleControl.updateOptions(event.oldPartOptions, event.newPartOptions); + // Title control Switch between showing tabs <=> not showing tabs if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) { - // Recreate title control - this.createTitleAreaControl(); - // Re-layout this.relayout(); // Ensure to show active editor if any if (this.model.activeEditor) { - this.titleAreaControl.openEditor(this.model.activeEditor); + this.titleControl.openEditor(this.model.activeEditor); } } - // Just update title control - else { - this.titleAreaControl.updateOptions(event.oldPartOptions, event.newPartOptions); - } - // Styles this.updateStyles(); @@ -739,13 +714,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.pinEditor(editor); // Forward to title control - this.titleAreaControl.updateEditorDirty(editor); + this.titleControl.updateEditorDirty(editor); } private onDidChangeEditorLabel(editor: EditorInput): void { // Forward to title control - this.titleAreaControl.updateEditorLabel(editor); + this.titleControl.updateEditorLabel(editor); } private onDidVisibilityChange(visible: boolean): void { @@ -780,7 +755,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get titleHeight(): IEditorGroupTitleHeight { - return this.titleAreaControl.getHeight(); + return this.titleControl.getHeight(); } notifyIndexChanged(newIndex: number): void { @@ -798,7 +773,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.element.classList.toggle('inactive', !isActive); // Update title control - this.titleAreaControl.setActive(isActive); + this.titleControl.setActive(isActive); // Update styles this.updateStyles(); @@ -925,7 +900,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editor) { - this.titleAreaControl.pinEditor(editor); + this.titleControl.pinEditor(editor); } } } @@ -952,14 +927,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // title control and also make sure to emit this as an event const newIndexOfEditor = this.getIndexOfEditor(editor); if (newIndexOfEditor !== oldIndexOfEditor) { - this.titleAreaControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor); + this.titleControl.moveEditor(editor, oldIndexOfEditor, newIndexOfEditor); } // Forward sticky state to title control if (sticky) { - this.titleAreaControl.stickEditor(editor); + this.titleControl.stickEditor(editor); } else { - this.titleAreaControl.unstickEditor(editor); + this.titleControl.unstickEditor(editor); } } } @@ -1119,7 +1094,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Show in title control after editor control because some actions depend on it // but respect the internal options in case title control updates should skip. if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.openEditor(editor); + this.titleControl.openEditor(editor); } return openEditorPromise; @@ -1169,7 +1144,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { })); // Update the title control all at once with all editors - this.titleAreaControl.openEditors(inactiveEditors.map(({ editor }) => editor)); + this.titleControl.openEditors(inactiveEditors.map(({ editor }) => editor)); // Opening many editors at once can put any editor to be // the active one depending on options. As such, we simply @@ -1200,8 +1175,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // in source and target if the title update was skipped if (internalOptions.skipTitleUpdate) { const movedEditors = editors.map(({ editor }) => editor); - target.titleAreaControl.openEditors(movedEditors); - this.titleAreaControl.closeEditors(movedEditors); + target.titleControl.openEditors(movedEditors); + this.titleControl.closeEditors(movedEditors); } } @@ -1241,9 +1216,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.model.moveEditor(editor, moveToIndex); this.model.pin(editor); - // Forward to title area - this.titleAreaControl.moveEditor(editor, currentIndex, moveToIndex); - this.titleAreaControl.pinEditor(editor); + // Forward to title control + this.titleControl.moveEditor(editor, currentIndex, moveToIndex); + this.titleControl.pinEditor(editor); } private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: EditorGroupView, openOptions?: IEditorOpenOptions, internalOptions?: IInternalMoveCopyOptions): void { @@ -1299,7 +1274,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // in target if the title update was skipped if (internalOptions.skipTitleUpdate) { const copiedEditors = editors.map(({ editor }) => editor); - target.titleAreaControl.openEditors(copiedEditors); + target.titleControl.openEditors(copiedEditors); } } @@ -1346,7 +1321,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control unless skipped via internal options if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.beforeCloseEditor(editor); + this.titleControl.beforeCloseEditor(editor); } // Closing the active editor of the group is a bit more work @@ -1361,7 +1336,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control unless skipped via internal options if (!internalOptions?.skipTitleUpdate) { - this.titleAreaControl.closeEditor(editor); + this.titleControl.closeEditor(editor); } } @@ -1711,7 +1686,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editors.length) { - this.titleAreaControl.closeEditors(editors); + this.titleControl.closeEditors(editors); } } @@ -1763,7 +1738,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Forward to title control if (editorsToClose.length) { - this.titleAreaControl.closeEditors(editorsToClose); + this.titleControl.closeEditors(editorsToClose); } } @@ -1922,16 +1897,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.lastLayout = { width, height, top, left }; this.element.classList.toggle('max-height-478px', height <= 478); - // Layout the title area first to receive the size it occupies - const titleAreaSize = this.titleAreaControl.layout({ + // Layout the title control first to receive the size it occupies + const titleControlSize = this.titleControl.layout({ container: new Dimension(width, height), available: new Dimension(width, height - this.editorPane.minimumHeight) }); // Pass the container width and remaining height to the editor layout - const editorHeight = Math.max(0, height - titleAreaSize.height); + const editorHeight = Math.max(0, height - titleControlSize.height); this.editorContainer.style.height = `${editorHeight}px`; - this.editorPane.layout({ width, height: editorHeight, top: top + titleAreaSize.height, left }); + this.editorPane.layout({ width, height: editorHeight, top: top + titleControlSize.height, left }); } relayout(): void { @@ -1956,8 +1931,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onWillDispose.fire(); - this.titleAreaControl.dispose(); - super.dispose(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index a73ebcd2053..5b9869395f9 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -42,7 +42,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; import { Promises, timeout } from 'vs/base/common/async'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IMarker, IMarkerService, MarkerSeverity, IMarkerData } from 'vs/platform/markers/common/markers'; @@ -54,7 +54,7 @@ import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { TabFocusMode } from 'vs/workbench/browser/parts/editor/tabFocus'; +import { TabFocus } from 'vs/editor/browser/config/tabFocus'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { } @@ -278,6 +278,36 @@ class State { } } +class TabFocusMode extends Disposable { + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + constructor(@IConfigurationService private readonly configurationService: IConfigurationService) { + super(); + + this.registerListeners(); + + const tabFocusModeConfig = configurationService.getValue('editor.tabFocusMode') === true ? true : false; + TabFocus.setTabFocusMode(tabFocusModeConfig); + + this._onDidChange.fire(tabFocusModeConfig); + } + + private registerListeners(): void { + this._register(TabFocus.onDidChangeTabFocus(tabFocusMode => this._onDidChange.fire(tabFocusMode))); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.tabFocusMode')) { + const tabFocusModeConfig = this.configurationService.getValue('editor.tabFocusMode') === true ? true : false; + TabFocus.setTabFocusMode(tabFocusModeConfig); + + this._onDidChange.fire(tabFocusModeConfig); + } + })); + } +} + const nlsSingleSelectionRange = localize('singleSelectionRange', "Ln {0}, Col {1} ({2} selected)"); const nlsSingleSelection = localize('singleSelection', "Ln {0}, Col {1}"); const nlsMultiSelectionRange = localize('multiSelectionRange', "{0} selections ({1} characters selected)"); diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts similarity index 83% rename from src/vs/workbench/browser/parts/editor/titleControl.ts rename to src/vs/workbench/browser/parts/editor/editorTabsControl.ts index d09cbb7008b..67e142dea05 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/titlecontrol'; +import 'vs/css!./media/editortabscontrol'; import { localize } from 'vs/nls'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; @@ -11,10 +11,9 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, SubmenuAction, ActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { createActionViewItem, 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 { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -25,14 +24,11 @@ import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; -import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; -import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { IFileService } from 'vs/platform/files/common/files'; import { assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; import { isCancellationError } from 'vs/base/common/errors'; @@ -41,24 +37,11 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { LocalSelectionTransfer } from 'vs/platform/dnd/browser/dnd'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; export interface IToolbarActions { - primary: IAction[]; - secondary: IAction[]; -} - -export interface ITitleControlDimensions { - - /** - * The size of the parent container the title control is layed out in. - */ - container: Dimension; - - /** - * The maximum size the title control is allowed to consume based on - * other controls that are positioned inside the container. - */ - available: Dimension; + readonly primary: IAction[]; + readonly secondary: IAction[]; } export class EditorCommandsContextActionRunner extends ActionRunner { @@ -87,19 +70,17 @@ export class EditorCommandsContextActionRunner extends ActionRunner { } } -export abstract class TitleControl extends Themable { +export abstract class EditorTabsControl extends Themable { protected readonly editorTransfer = LocalSelectionTransfer.getInstance(); protected readonly groupTransfer = LocalSelectionTransfer.getInstance(); protected readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); - private static readonly EDITOR_TITLE_HEIGHT = { - normal: 35, - compact: 22 + private static readonly EDITOR_TAB_HEIGHT = { + normal: 35 as const, + compact: 22 as const }; - protected breadcrumbsControl: BreadcrumbsControl | undefined = undefined; - private editorActionsToolbar: WorkbenchToolBar | undefined; private resourceContext: ResourceContextKey; @@ -131,8 +112,6 @@ export abstract class TitleControl extends Themable { @IMenuService private readonly menuService: IMenuService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IConfigurationService protected configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService ) { super(themeService); @@ -156,41 +135,9 @@ export abstract class TitleControl extends Themable { } protected create(parent: HTMLElement): void { - this.updateTitleHeight(); + this.updateTabHeight(); } - protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void { - const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService)); - this._register(config.onDidChange(() => { - const value = config.getValue(); - if (!value && this.breadcrumbsControl) { - this.breadcrumbsControl.dispose(); - this.breadcrumbsControl = undefined; - this.handleBreadcrumbsEnablementChange(); - } else if (value && !this.breadcrumbsControl) { - this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group); - this.breadcrumbsControl.update(); - this.handleBreadcrumbsEnablementChange(); - } - })); - - if (config.getValue()) { - this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group); - } - - this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (this.breadcrumbsControl?.model && this.breadcrumbsControl.model.resource.scheme !== e.scheme) { - // ignore if the scheme of the breadcrumbs resource is not affected - return; - } - if (this.breadcrumbsControl?.update()) { - this.handleBreadcrumbsEnablementChange(); - } - })); - } - - protected abstract handleBreadcrumbsEnablementChange(): void; - protected createEditorActionsToolBar(container: HTMLElement): void { const context: IEditorCommandsContext = { groupId: this.group.id }; @@ -429,25 +376,25 @@ export abstract class TitleControl extends Themable { return keybinding ? keybinding.getLabel() ?? undefined : undefined; } - protected get titleHeight() { - return this.accessor.partOptions.tabHeight !== 'compact' ? TitleControl.EDITOR_TITLE_HEIGHT.normal : TitleControl.EDITOR_TITLE_HEIGHT.compact; + protected get tabHeight() { + return this.accessor.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } - protected updateTitleHeight(): void { - this.parent.style.setProperty('--editor-group-title-height', `${this.titleHeight}px`); + protected updateTabHeight(): void { + this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`); } updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { - // Update title height + // Update tab height if (oldOptions.tabHeight !== newOptions.tabHeight) { - this.updateTitleHeight(); + this.updateTabHeight(); } } - abstract openEditor(editor: EditorInput): void; + abstract openEditor(editor: EditorInput): boolean; - abstract openEditors(editors: EditorInput[]): void; + abstract openEditors(editors: EditorInput[]): boolean; abstract beforeCloseEditor(editor: EditorInput): void; @@ -469,14 +416,7 @@ export abstract class TitleControl extends Themable { abstract updateEditorDirty(editor: EditorInput): void; - abstract layout(dimensions: ITitleControlDimensions): Dimension; + abstract layout(dimensions: IEditorTitleControlDimensions): Dimension; - abstract getHeight(): IEditorGroupTitleHeight; - - override dispose(): void { - dispose(this.breadcrumbsControl); - this.breadcrumbsControl = undefined; - - super.dispose(); - } + abstract getHeight(): number; } diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts new file mode 100644 index 00000000000..ecdc4f67a57 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/editortitlecontrol'; +import { Dimension, clearNode } from 'vs/base/browser/dom'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; +import { BreadcrumbsControl, BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; +import { IEditorGroupsAccessor, IEditorGroupTitleHeight, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { MultiEditorTabsControl } from 'vs/workbench/browser/parts/editor/multiEditorTabsControl'; +import { SingleEditorTabsControl } from 'vs/workbench/browser/parts/editor/singleEditorTabsControl'; +import { IEditorPartOptions } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +export interface IEditorTitleControlDimensions { + + /** + * The size of the parent container the title control is layed out in. + */ + readonly container: Dimension; + + /** + * The maximum size the title control is allowed to consume based on + * other controls that are positioned inside the container. + */ + readonly available: Dimension; +} + +export class EditorTitleControl extends Themable { + + private editorTabsControl: EditorTabsControl; + private editorTabsControlDisposable = this._register(new DisposableStore()); + + private breadcrumbsControlFactory: BreadcrumbsControlFactory | undefined; + private breadcrumbsControlDisposables = this._register(new DisposableStore()); + private get breadcrumbsControl() { return this.breadcrumbsControlFactory?.control; } + + constructor( + private parent: HTMLElement, + private accessor: IEditorGroupsAccessor, + private group: IEditorGroupView, + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService + ) { + super(themeService); + + this.editorTabsControl = this.createEditorTabsControl(); + this.breadcrumbsControlFactory = this.createBreadcrumbsControl(); + } + + private createEditorTabsControl(): EditorTabsControl { + let control: EditorTabsControl; + if (this.accessor.partOptions.showTabs) { + control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.accessor, this.group); + } else { + control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.accessor, this.group); + } + + return this.editorTabsControlDisposable.add(control); + } + + private createBreadcrumbsControl(): BreadcrumbsControlFactory | undefined { + if (!this.accessor.partOptions.showTabs) { + return undefined; // single tabs have breadcrumbs inlined + } + + // Breadcrumbs container + const breadcrumbsContainer = document.createElement('div'); + breadcrumbsContainer.classList.add('breadcrumbs-below-tabs'); + this.parent.appendChild(breadcrumbsContainer); + + const breadcrumbsControlFactory = this.breadcrumbsControlDisposables.add(this.instantiationService.createInstance(BreadcrumbsControlFactory, breadcrumbsContainer, this.group, { + showFileIcons: true, + showSymbolIcons: true, + showDecorationColors: false, + showPlaceholder: true + })); + this.breadcrumbsControlDisposables.add(breadcrumbsControlFactory.onDidEnablementChange(() => this.handleBreadcrumbsEnablementChange())); + + return breadcrumbsControlFactory; + } + + private handleBreadcrumbsEnablementChange(): void { + this.group.relayout(); // relayout when breadcrumbs are enable/disabled + } + + openEditor(editor: EditorInput): void { + const didChange = this.editorTabsControl.openEditor(editor); + + this.handleOpenedEditors(didChange); + } + + openEditors(editors: EditorInput[]): void { + const didChange = this.editorTabsControl.openEditors(editors); + + this.handleOpenedEditors(didChange); + } + + private handleOpenedEditors(didChange: boolean): void { + if (didChange) { + this.breadcrumbsControl?.update(); + } else { + this.breadcrumbsControl?.revealLast(); + } + } + + beforeCloseEditor(editor: EditorInput): void { + return this.editorTabsControl.beforeCloseEditor(editor); + } + + closeEditor(editor: EditorInput): void { + this.editorTabsControl.closeEditor(editor); + + this.handleClosedEditors(); + } + + closeEditors(editors: EditorInput[]): void { + this.editorTabsControl.closeEditors(editors); + + this.handleClosedEditors(); + } + + private handleClosedEditors(): void { + if (!this.group.activeEditor) { + this.breadcrumbsControl?.update(); + } + } + + moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number): void { + return this.editorTabsControl.moveEditor(editor, fromIndex, targetIndex); + } + + pinEditor(editor: EditorInput): void { + return this.editorTabsControl.pinEditor(editor); + } + + stickEditor(editor: EditorInput): void { + return this.editorTabsControl.stickEditor(editor); + } + + unstickEditor(editor: EditorInput): void { + return this.editorTabsControl.unstickEditor(editor); + } + + setActive(isActive: boolean): void { + return this.editorTabsControl.setActive(isActive); + } + + updateEditorLabel(editor: EditorInput): void { + return this.editorTabsControl.updateEditorLabel(editor); + } + + updateEditorDirty(editor: EditorInput): void { + return this.editorTabsControl.updateEditorDirty(editor); + } + + updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void { + + // Update editor tabs control if options changed + if (oldOptions.showTabs !== newOptions.showTabs) { + + // Clear old + this.editorTabsControlDisposable.clear(); + this.breadcrumbsControlDisposables.clear(); + clearNode(this.parent); + + // Create new + this.editorTabsControl = this.createEditorTabsControl(); + this.breadcrumbsControlFactory = this.createBreadcrumbsControl(); + } + + // Forward into editor tabs control + this.editorTabsControl.updateOptions(oldOptions, newOptions); + } + + layout(dimensions: IEditorTitleControlDimensions): Dimension { + + // Layout tabs control + const tabsControlDimension = this.editorTabsControl.layout(dimensions); + + // Layout breadcrumbs if visible + let breadcrumbsControlDimension: Dimension | undefined = undefined; + if (this.breadcrumbsControl?.isHidden() === false) { + breadcrumbsControlDimension = new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT); + this.breadcrumbsControl.layout(breadcrumbsControlDimension); + } + + return new Dimension( + dimensions.container.width, + tabsControlDimension.height + (breadcrumbsControlDimension ? breadcrumbsControlDimension.height : 0) + ); + } + + getHeight(): IEditorGroupTitleHeight { + const tabsControlHeight = this.editorTabsControl.getHeight(); + const breadcrumbsControlHeight = this.breadcrumbsControl?.isHidden() === false ? BreadcrumbsControl.HEIGHT : 0; + + return { + total: tabsControlHeight + breadcrumbsControlHeight, + offset: tabsControlHeight + }; + } +} diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css similarity index 95% rename from src/vs/workbench/browser/parts/editor/media/titlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/editortabscontrol.css index 0b22793aacd..bf44c02aee2 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/editortabscontrol.css @@ -30,7 +30,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .monaco-icon-label::before { - height: var(--editor-group-title-height); /* tweak the icon size of the editor labels when icons are enabled */ + height: var(--editor-group-tab-height); /* tweak the icon size of the editor labels when icons are enabled */ } .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .monaco-icon-label::after, diff --git a/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css new file mode 100644 index 00000000000..a24f76131a8 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/editortitlecontrol.css @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Breadcrumbs (below multiple editor tabs) */ + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control { + flex: 1 100%; + height: 22px; + cursor: default; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-icon-label { + height: 22px; + line-height: 22px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-icon-label::before { + height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .outline-element-icon { + padding-right: 3px; + height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ + line-height: 22px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item { + max-width: 80%; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item::before { + width: 16px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item:last-child { + padding-right: 8px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .breadcrumbs-below-tabs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { + display: none; /* hides chevrons when last item */ +} diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css similarity index 92% rename from src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index f5b39db85fc..d51863edf23 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -71,7 +71,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container { display: flex; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); scrollbar-width: none; /* Firefox: hide scrollbar */ } @@ -97,7 +97,7 @@ display: flex; white-space: nowrap; cursor: pointer; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); box-sizing: border-box; padding-left: 10px; } @@ -265,7 +265,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label { margin-top: auto; margin-bottom: auto; - line-height: var(--editor-group-title-height); /* aligns icon and label vertically centered in the tab */ + line-height: var(--editor-group-tab-height); /* aligns icon and label vertically centered in the tab */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink .tab-label, @@ -424,56 +424,13 @@ pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } -/* Breadcrumbs */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control { - flex: 1 100%; - height: 22px; - cursor: default; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label { - height: 22px; - line-height: 22px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before { - height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .outline-element-icon { - padding-right: 3px; - height: 22px; /* tweak the icon size of the editor labels when icons are enabled */ - line-height: 22px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item { - max-width: 80%; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { - width: 16px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child { - padding-right: 8px; -} - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child .codicon:last-child { - display: none; /* hides chevrons when last item */ -} - /* Editor Actions Toolbar */ .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { cursor: default; flex: initial; padding: 0 8px 0 4px; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); } .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-item { diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css similarity index 96% rename from src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css rename to src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css index 81562a81281..893920c71c5 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css @@ -6,7 +6,7 @@ /* Title Label */ .monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container { - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); display: flex; justify-content: flex-start; align-items: center; @@ -15,7 +15,7 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title > .label-container > .title-label { - line-height: var(--editor-group-title-height); + line-height: var(--editor-group-tab-height); overflow: hidden; text-overflow: ellipsis; position: relative; @@ -26,7 +26,7 @@ flex: initial; /* helps to show decorations right next to the label and not at the end while still preserving text overflow ellipsis */ } -/* Breadcrumbs */ +/* Breadcrumbs (inline next to single editor tab) */ .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label { flex: none; @@ -94,7 +94,7 @@ flex: initial; opacity: 0.5; padding-right: 8px; - height: var(--editor-group-title-height); + height: var(--editor-group-tab-height); } .monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions .action-item { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts similarity index 94% rename from src/vs/workbench/browser/parts/editor/tabsTitleControl.ts rename to src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index ac3fced8d68..e3e4de2bc0e 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/tabstitlecontrol'; +import 'vs/css!./media/multieditortabscontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor'; @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { EditorCommandsContextActionRunner, ITitleControlDimensions, IToolbarActions, TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; +import { EditorCommandsContextActionRunner, IToolbarActions, EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -34,11 +34,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; -import { IFileService } from 'vs/platform/files/common/files'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; @@ -55,46 +52,47 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; interface IEditorInputLabel { - editor: EditorInput; + readonly editor: EditorInput; - name?: string; + readonly name?: string; description?: string; - forceDescription?: boolean; - title?: string; - ariaLabel?: string; + readonly forceDescription?: boolean; + readonly title?: string; + readonly ariaLabel?: string; } -interface ITabsTitleControlLayoutOptions { +interface IMultiEditorTabsControlLayoutOptions { /** * Whether to force revealing the active tab, even when * the dimensions have not changed. This can be the case * when a tab was made active and needs to be revealed. */ - forceRevealActiveTab?: true; + readonly forceRevealActiveTab?: true; } -interface IScheduledTabsTitleControlLayout extends IDisposable { +interface IScheduledMultiEditorTabsControlLayout extends IDisposable { /** * Associated options with the layout call. */ - options?: ITabsTitleControlLayoutOptions; + options?: IMultiEditorTabsControlLayoutOptions; } -export class TabsTitleControl extends TitleControl { +export class MultiEditorTabsControl extends EditorTabsControl { private static readonly SCROLLBAR_SIZES = { - default: 3, - large: 10 + default: 3 as const, + large: 10 as const }; private static readonly TAB_WIDTH = { - compact: 38, - shrink: 80, - fit: 120 + compact: 38 as const, + shrink: 80 as const, + fit: 120 as const }; private static readonly DRAG_OVER_OPEN_TAB_THRESHOLD = 1500; @@ -119,12 +117,12 @@ export class TabsTitleControl extends TitleControl { private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; - private dimensions: ITitleControlDimensions & { used?: Dimension } = { + private dimensions: IEditorTitleControlDimensions & { used?: Dimension } = { container: Dimension.None, available: Dimension.None }; - private readonly layoutScheduler = this._register(new MutableDisposable()); + private readonly layoutScheduler = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean | undefined; private path: IPath = isWindows ? win32 : posix; @@ -144,15 +142,13 @@ export class TabsTitleControl extends TitleControl { @IMenuService menuService: IMenuService, @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IFileService fileService: IFileService, @IEditorService private readonly editorService: EditorServiceImpl, @IPathService private readonly pathService: IPathService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, configurationService, fileService, editorResolverService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -197,12 +193,6 @@ export class TabsTitleControl extends TitleControl { // Editor Actions Toolbar this.createEditorActionsToolBar(this.editorToolbarContainer); - - // Breadcrumbs - const breadcrumbsContainer = document.createElement('div'); - breadcrumbsContainer.classList.add('tabs-breadcrumbs'); - this.titleContainer.appendChild(breadcrumbsContainer); - this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, showPlaceholder: true }); } private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { @@ -271,10 +261,10 @@ export class TabsTitleControl extends TitleControl { private getTabsScrollbarSizing(): number { if (this.accessor.partOptions.titleScrollbarSizing !== 'large') { - return TabsTitleControl.SCROLLBAR_SIZES.default; + return MultiEditorTabsControl.SCROLLBAR_SIZES.default; } - return TabsTitleControl.SCROLLBAR_SIZES.large; + return MultiEditorTabsControl.SCROLLBAR_SIZES.large; } private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void { @@ -421,7 +411,7 @@ export class TabsTitleControl extends TitleControl { // The restriction is relaxed according to the absolute value of `deltaX` and `deltaY` // to support discrete (mouse wheel) and contiguous scrolling (touchpad) equally well const now = Date.now(); - if (now - this.lastMouseWheelEventTime < TabsTitleControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) { + if (now - this.lastMouseWheelEventTime < MultiEditorTabsControl.MOUSE_WHEEL_EVENT_THRESHOLD - 2 * (Math.abs(e.deltaX) + Math.abs(e.deltaY))) { return; } @@ -429,9 +419,9 @@ export class TabsTitleControl extends TitleControl { // Figure out scrolling direction but ignore it if too subtle let tabSwitchDirection: number; - if (e.deltaX + e.deltaY < - TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + if (e.deltaX + e.deltaY < - MultiEditorTabsControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { tabSwitchDirection = -1; - } else if (e.deltaX + e.deltaY > TabsTitleControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { + } else if (e.deltaX + e.deltaY > MultiEditorTabsControl.MOUSE_WHEEL_DISTANCE_THRESHOLD) { tabSwitchDirection = 1; } else { return; @@ -490,15 +480,15 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimensions); } - openEditor(editor: EditorInput): void { - this.handleOpenedEditors(); + openEditor(editor: EditorInput): boolean { + return this.handleOpenedEditors(); } - openEditors(editors: EditorInput[]): void { - this.handleOpenedEditors(); + openEditors(editors: EditorInput[]): boolean { + return this.handleOpenedEditors(); } - private handleOpenedEditors(): void { + private handleOpenedEditors(): boolean { // Create tabs as needed const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); @@ -508,7 +498,7 @@ export class TabsTitleControl extends TitleControl { // Make sure to recompute tab labels and detect // if a label change occurred that requires a - // redraw of tabs and update of breadcrumbs. + // redraw of tabs. const activeEditorChanged = this.didActiveEditorChange(); const oldActiveTabLabel = this.activeTabLabel; @@ -516,20 +506,22 @@ export class TabsTitleControl extends TitleControl { this.computeTabLabels(); // Redraw and update in these cases + let didChange = false; if ( activeEditorChanged || // active editor changed oldTabLabelsLength !== this.tabLabels.length || // number of tabs changed !this.equalsEditorInputLabel(oldActiveTabLabel, this.activeTabLabel) // active editor label changed ) { this.redraw({ forceRevealActiveTab: true }); - this.breadcrumbsControl?.update(); + didChange = true; } - // Otherwise only layout for revealing in tabs and breadcrumbs + // Otherwise only layout for revealing else { this.layout(this.dimensions, { forceRevealActiveTab: true }); - this.breadcrumbsControl?.revealLast(); } + + return didChange; } private didActiveEditorChange(): boolean { @@ -617,8 +609,6 @@ export class TabsTitleControl extends TitleControl { this.tabActionBars = []; this.clearEditorActionsToolbar(); - - this.breadcrumbsControl?.update(); } } @@ -1052,7 +1042,7 @@ export class TabsTitleControl extends TitleControl { }, onDragOver: (_, dragDuration) => { - if (dragDuration >= TabsTitleControl.DRAG_OVER_OPEN_TAB_THRESHOLD) { + if (dragDuration >= MultiEditorTabsControl.DRAG_OVER_OPEN_TAB_THRESHOLD) { const draggedOverTab = this.group.getEditorByIndex(index); if (draggedOverTab && this.group.activeEditor !== draggedOverTab) { this.group.openEditor(draggedOverTab, { preserveFocus: true }); @@ -1089,7 +1079,7 @@ export class TabsTitleControl extends TitleControl { if (Array.isArray(data)) { const group = data[0]; if (group.identifier === this.group.id) { - return false; // groups cannot be dropped on title area it originates from + return false; // groups cannot be dropped on group it originates from } } @@ -1251,7 +1241,7 @@ export class TabsTitleControl extends TitleControl { } } - private redraw(options?: ITabsTitleControlLayoutOptions): void { + private redraw(options?: IMultiEditorTabsControlLayoutOptions): void { // Border below tabs if any with explicit high contrast support if (this.tabsAndActionsContainer) { @@ -1322,10 +1312,10 @@ export class TabsTitleControl extends TitleControl { let stickyTabWidth = 0; switch (options.pinnedTabSizing) { case 'compact': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.compact; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.compact; break; case 'shrink': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.shrink; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.shrink; break; } @@ -1525,15 +1515,11 @@ export class TabsTitleControl extends TitleControl { } } - getHeight(): IEditorGroupTitleHeight { - const showsBreadcrumbs = this.breadcrumbsControl && !this.breadcrumbsControl.isHidden(); + getHeight(): number { // Return quickly if our used dimensions are known if (this.dimensions.used) { - return { - total: this.dimensions.used.height, - offset: showsBreadcrumbs ? this.dimensions.used.height - BreadcrumbsControl.HEIGHT : this.dimensions.used.height - }; + return this.dimensions.used.height; } // Otherwise compute via browser APIs @@ -1542,28 +1528,21 @@ export class TabsTitleControl extends TitleControl { } } - private computeHeight(): IEditorGroupTitleHeight { - let total: number; + private computeHeight(): number { + let height: number; // Wrap: we need to ask `offsetHeight` to get // the real height of the title area with wrapping. if (this.accessor.partOptions.wrapTabs && this.tabsAndActionsContainer?.classList.contains('wrapping')) { - total = this.tabsAndActionsContainer.offsetHeight; + height = this.tabsAndActionsContainer.offsetHeight; } else { - total = this.titleHeight; + height = this.tabHeight; } - const offset = total; - - // Account for breadcrumbs if visible - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - total += BreadcrumbsControl.HEIGHT; // Account for breadcrumbs if visible - } - - return { total, offset }; + return height; } - layout(dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): Dimension { + layout(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): Dimension { // Remember dimensions that we get Object.assign(this.dimensions, dimensions); @@ -1591,21 +1570,18 @@ export class TabsTitleControl extends TitleControl { // First time layout: compute the dimensions and store it if (!this.dimensions.used) { - this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight().total); + this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight()); } return this.dimensions.used; } - private doLayout(dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): void { + private doLayout(dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void { // Only layout if we have valid tab index and dimensions const activeTabAndIndex = this.group.activeEditor ? this.getTabAndIndex(this.group.activeEditor) : undefined; if (activeTabAndIndex && dimensions.container !== Dimension.None && dimensions.available !== Dimension.None) { - // Breadcrumbs - this.doLayoutBreadcrumbs(dimensions); - // Tabs const [activeTab, activeIndex] = activeTabAndIndex; this.doLayoutTabs(activeTab, activeIndex, dimensions, options); @@ -1615,7 +1591,7 @@ export class TabsTitleControl extends TitleControl { // return it fast from the `layout` call without having to // compute it over and over again const oldDimension = this.dimensions.used; - const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight().total); + const newDimension = this.dimensions.used = new Dimension(dimensions.container.width, this.computeHeight()); // In case the height of the title control changed from before // (currently only possible if wrapping changed on/off), we need @@ -1626,17 +1602,7 @@ export class TabsTitleControl extends TitleControl { } } - protected handleBreadcrumbsEnablementChange(): void { - this.group.relayout(); // relayout when breadcrumbs are enable/disabled - } - - private doLayoutBreadcrumbs(dimensions: ITitleControlDimensions): void { - if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { - this.breadcrumbsControl.layout(new Dimension(dimensions.container.width, BreadcrumbsControl.HEIGHT)); - } - } - - private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: ITitleControlDimensions, options?: ITabsTitleControlLayoutOptions): void { + private doLayoutTabs(activeTab: HTMLElement, activeIndex: number, dimensions: IEditorTitleControlDimensions, options?: IMultiEditorTabsControlLayoutOptions): void { // Always first layout tabs with wrapping support even if wrapping // is disabled. The result indicates if tabs wrap and if not, we @@ -1649,7 +1615,7 @@ export class TabsTitleControl extends TitleControl { } } - private doLayoutTabsWrapping(dimensions: ITitleControlDimensions): boolean { + private doLayoutTabsWrapping(dimensions: IEditorTitleControlDimensions): boolean { const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar); // Handle wrapping tabs according to setting: @@ -1707,9 +1673,9 @@ export class TabsTitleControl extends TitleControl { // Tabs wrap multiline: remove wrapping under certain size constraint conditions if (tabsWrapMultiLine) { if ( - (tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height - (allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === this.titleHeight) || // if wrapping is not needed anymore - (!lastTabFitsWrapped()) // if last tab does not fit anymore + (tabsContainer.offsetHeight > dimensions.available.height) || // if height exceeds available height + (allTabsWidth === visibleTabsWidth && tabsContainer.offsetHeight === this.tabHeight) || // if wrapping is not needed anymore + (!lastTabFitsWrapped()) // if last tab does not fit anymore ) { updateTabsWrapping(false); } @@ -1778,7 +1744,7 @@ export class TabsTitleControl extends TitleControl { return tabsWrapMultiLine; } - private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number, options?: ITabsTitleControlLayoutOptions): void { + private doLayoutTabsNonWrapping(activeTab: HTMLElement, activeIndex: number, options?: IMultiEditorTabsControlLayoutOptions): void { const [tabsContainer, tabsScrollbar] = assertAllDefined(this.tabsContainer, this.tabsScrollbar); // @@ -1809,10 +1775,10 @@ export class TabsTitleControl extends TitleControl { let stickyTabWidth = 0; switch (this.accessor.partOptions.pinnedTabSizing) { case 'compact': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.compact; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.compact; break; case 'shrink': - stickyTabWidth = TabsTitleControl.TAB_WIDTH.shrink; + stickyTabWidth = MultiEditorTabsControl.TAB_WIDTH.shrink; break; } @@ -1827,7 +1793,7 @@ export class TabsTitleControl extends TitleControl { // is little enough that we need to disable sticky tabs sticky positioning // so that tabs can be scrolled at naturally. let availableTabsContainerWidth = visibleTabsWidth - stickyTabsWidth; - if (this.group.stickyCount > 0 && availableTabsContainerWidth < TabsTitleControl.TAB_WIDTH.fit) { + if (this.group.stickyCount > 0 && availableTabsContainerWidth < MultiEditorTabsControl.TAB_WIDTH.fit) { tabsContainer.classList.add('disable-sticky-tabs'); availableTabsContainerWidth = visibleTabsWidth; @@ -2153,7 +2119,7 @@ registerThemingParticipant((theme, collector) => { // Hover Border // // Unfortunately we need to copy a lot of CSS over from the - // tabsTitleControl.css because we want to reuse the same + // multiEditorTabsControl.css because we want to reuse the same // styles we already have for the normal bottom-border. const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER); if (tabHoverBorder) { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts similarity index 88% rename from src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts rename to src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 460698f6749..9912e00a295 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/notabstitlecontrol'; +import 'vs/css!./media/singleeditortabscontrol'; import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { TitleControl, IToolbarActions, ITitleControlDimensions } from 'vs/workbench/browser/parts/editor/titleControl'; +import { EditorTabsControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; @@ -14,29 +14,33 @@ import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor } import { CLOSE_EDITOR_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; -import { IEditorGroupTitleHeight } from 'vs/workbench/browser/parts/editor/editor'; import { equals } from 'vs/base/common/objects'; import { toDisposable } from 'vs/base/common/lifecycle'; import { defaultBreadcrumbsWidgetStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; +import { BreadcrumbsControlFactory } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; interface IRenderedEditorLabel { - editor?: EditorInput; - pinned: boolean; + readonly editor?: EditorInput; + readonly pinned: boolean; } -export class NoTabsTitleControl extends TitleControl { +export class SingleEditorTabsControl extends EditorTabsControl { private titleContainer: HTMLElement | undefined; private editorLabel: IResourceLabel | undefined; private activeLabel: IRenderedEditorLabel = Object.create(null); + private breadcrumbsControlFactory: BreadcrumbsControlFactory | undefined; + private get breadcrumbsControl() { return this.breadcrumbsControlFactory?.control; } + protected override create(parent: HTMLElement): void { super.create(parent); const titleContainer = this.titleContainer = parent; titleContainer.draggable = true; - //Container listeners + // Container listeners this.registerContainerListeners(titleContainer); // Gesture Support @@ -51,7 +55,14 @@ export class NoTabsTitleControl extends TitleControl { this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs - this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, widgetStyles: { ...defaultBreadcrumbsWidgetStyles, breadcrumbsBackground: Color.transparent.toString() }, showPlaceholder: false }); + this.breadcrumbsControlFactory = this._register(this.instantiationService.createInstance(BreadcrumbsControlFactory, labelContainer, this.group, { + showFileIcons: false, + showSymbolIcons: true, + showDecorationColors: false, + widgetStyles: { ...defaultBreadcrumbsWidgetStyles, breadcrumbsBackground: Color.transparent.toString() }, + showPlaceholder: false + })); + this._register(this.breadcrumbsControlFactory.onDidEnablementChange(() => this.handleBreadcrumbsEnablementChange())); titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); this._register(toDisposable(() => titleContainer.classList.remove('breadcrumbs'))); // important to remove because the container is a shared dom node @@ -130,19 +141,21 @@ export class NoTabsTitleControl extends TitleControl { setTimeout(() => this.quickInputService.quickAccess.show(), 50); } - openEditor(editor: EditorInput): void { - this.doHandleOpenEditor(); + openEditor(editor: EditorInput): boolean { + return this.doHandleOpenEditor(); } - openEditors(editors: EditorInput[]): void { - this.doHandleOpenEditor(); + openEditors(editors: EditorInput[]): boolean { + return this.doHandleOpenEditor(); } - private doHandleOpenEditor(): void { + private doHandleOpenEditor(): boolean { const activeEditorChanged = this.ifActiveEditorChanged(() => this.redraw()); if (!activeEditorChanged) { this.ifActiveEditorPropertiesChanged(() => this.redraw()); } + + return activeEditorChanged; } beforeCloseEditor(editor: EditorInput): void { @@ -348,16 +361,13 @@ export class NoTabsTitleControl extends TitleControl { } } - getHeight(): IEditorGroupTitleHeight { - return { - total: this.titleHeight, - offset: 0 - }; + getHeight(): number { + return this.tabHeight; } - layout(dimensions: ITitleControlDimensions): Dimension { + layout(dimensions: IEditorTitleControlDimensions): Dimension { this.breadcrumbsControl?.layout(undefined); - return new Dimension(dimensions.container.width, this.getHeight().total); + return new Dimension(dimensions.container.width, this.getHeight()); } } diff --git a/src/vs/workbench/browser/parts/editor/tabFocus.ts b/src/vs/workbench/browser/parts/editor/tabFocus.ts deleted file mode 100644 index 79ad04a21dd..00000000000 --- a/src/vs/workbench/browser/parts/editor/tabFocus.ts +++ /dev/null @@ -1,29 +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 { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { TabFocus } from 'vs/editor/browser/config/tabFocus'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -export class TabFocusMode extends Disposable { - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - constructor(@IConfigurationService configurationService: IConfigurationService) { - super(); - TabFocus.onDidChangeTabFocus((tabFocusMode) => this._onDidChange.fire(tabFocusMode)); - const editorConfig: boolean = configurationService.getValue('editor.tabFocusMode'); - TabFocus.setTabFocusMode(editorConfig); - this._onDidChange.fire(editorConfig); - this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.tabFocusMode')) { - const value: boolean = configurationService.getValue('editor.tabFocusMode'); - TabFocus.setTabFocusMode(value); - this._onDidChange.fire(value); - } - })); - } -} diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index 82d9a3dd1d6..1306b617009 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -8,21 +8,18 @@ import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; -import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { MenuId, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IInstantiationService } 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'; import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle'; -import { MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_FOREGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; export class CommandCenterControl { @@ -50,68 +47,8 @@ export class CommandCenterControl { }, telemetrySource: 'commandCenter', actionViewItemProvider: (action) => { - - if (action instanceof MenuItemAction && action.id === 'workbench.action.quickOpenWithModes') { - - class CommandCenterViewItem extends BaseActionViewItem { - - constructor(action: IAction, options: IBaseActionViewItemOptions) { - super(undefined, action, options); - } - - override render(container: HTMLElement): void { - super.render(container); - container.classList.add('command-center'); - - // icon (search) - const searchIcon = renderIcon(Codicon.search); - searchIcon.classList.add('search-icon'); - - // label: just workspace name and optional decorations - const label = this._getLabel(); - const labelElement = document.createElement('span'); - labelElement.classList.add('search-label'); - labelElement.innerText = label; - reset(container, searchIcon, labelElement); - - const hover = this._store.add(setupCustomHover(hoverDelegate, container, this.getTooltip())); - - // update label & tooltip when window title changes - this._store.add(windowTitle.onDidChange(() => { - hover.update(this.getTooltip()); - labelElement.innerText = this._getLabel(); - })); - } - - private _getLabel(): string { - const { prefix, suffix } = windowTitle.getTitleDecorations(); - let label = windowTitle.isCustomTitleFormat() ? windowTitle.getWindowTitle() : windowTitle.workspaceName; - if (!label) { - label = localize('label.dfl', "Search"); - } - if (prefix) { - label = localize('label1', "{0} {1}", prefix, label); - } - if (suffix) { - label = localize('label2', "{0} {1}", label, suffix); - } - return label; - } - - protected override getTooltip() { - - // tooltip: full windowTitle - const kb = keybindingService.lookupKeybinding(action.id)?.getLabel(); - const title = kb - ? localize('title', "Search {0} ({1}) \u2014 {2}", windowTitle.workspaceName, kb, windowTitle.value) - : localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value); - - return title; - } - } - - return instantiationService.createInstance(CommandCenterViewItem, action, {}); - + if (action instanceof SubmenuItemAction && action.item.submenu === MenuId.CommandCenterCenter) { + return instantiationService.createInstance(CommandCenterCenterViewItem, action, windowTitle, hoverDelegate, {}); } else { return createActionViewItem(instantiationService, action, { hoverDelegate }); } @@ -134,56 +71,143 @@ export class CommandCenterControl { } -// --- theme colors +class CommandCenterCenterViewItem extends BaseActionViewItem { -// foreground (inactive and active) -colors.registerColor( - 'commandCenter.foreground', - { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, - localize('commandCenter-foreground', "Foreground color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeForeground', - { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, - localize('commandCenter-activeForeground', "Active foreground color of the command center"), - false -); -colors.registerColor( - 'commandCenter.inactiveForeground', - { dark: TITLE_BAR_INACTIVE_FOREGROUND, hcDark: TITLE_BAR_INACTIVE_FOREGROUND, light: TITLE_BAR_INACTIVE_FOREGROUND, hcLight: TITLE_BAR_INACTIVE_FOREGROUND }, - localize('commandCenter-inactiveForeground', "Foreground color of the command center when the window is inactive"), - false -); -// background (inactive and active) -colors.registerColor( - 'commandCenter.background', - { - dark: Color.white.transparent(0.05), hcDark: null, light: Color.black.transparent(0.05), hcLight: null - }, - localize('commandCenter-background', "Background color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeBackground', - { dark: Color.white.transparent(0.08), hcDark: MENUBAR_SELECTION_BACKGROUND, light: Color.black.transparent(0.08), hcLight: MENUBAR_SELECTION_BACKGROUND }, - localize('commandCenter-activeBackground', "Active background color of the command center"), - false -); -// border: active and inactive. defaults to active background -colors.registerColor( - 'commandCenter.border', { dark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60), light: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60) }, - localize('commandCenter-border', "Border color of the command center"), - false -); -colors.registerColor( - 'commandCenter.activeBorder', { dark: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: colors.transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, - localize('commandCenter-activeBorder', "Active border color of the command center"), - false -); -// border: defaults to active background -colors.registerColor( - 'commandCenter.inactiveBorder', { dark: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcDark: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), light: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcLight: colors.transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25) }, - localize('commandCenter-inactiveBorder', "Border color of the command center when the window is inactive"), - false -); + private static readonly _quickOpenCommandId = 'workbench.action.quickOpenWithModes'; + + constructor( + private readonly _submenu: SubmenuItemAction, + private readonly _windowTitle: WindowTitle, + private readonly _hoverDelegate: IHoverDelegate, + options: IBaseActionViewItemOptions, + @IKeybindingService private _keybindingService: IKeybindingService, + @IInstantiationService private _instaService: IInstantiationService, + ) { + super(undefined, _submenu.actions[0], options); + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.add('command-center-center'); + container.classList.toggle('multiple', (this._submenu.actions.length > 1)); + + const hover = this._store.add(setupCustomHover(this._hoverDelegate, container, this.getTooltip())); + + // update label & tooltip when window title changes + this._store.add(this._windowTitle.onDidChange(() => { + hover.update(this.getTooltip()); + })); + + const groups: (readonly IAction[])[] = []; + for (const action of this._submenu.actions) { + if (action instanceof SubmenuAction) { + groups.push(action.actions); + } else { + groups.push([action]); + } + } + + + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + + // nested toolbar + const toolbar = this._instaService.createInstance(WorkbenchToolBar, container, { + hiddenItemStrategy: HiddenItemStrategy.NoHide, + telemetrySource: 'commandCenterCenter', + actionViewItemProvider: (action, options) => { + options = { + ...options, + hoverDelegate: this._hoverDelegate, + }; + + if (action.id !== CommandCenterCenterViewItem._quickOpenCommandId) { + return createActionViewItem(this._instaService, action, options); + } + + const that = this; + + return this._instaService.createInstance(class CommandCenterQuickPickItem extends BaseActionViewItem { + + constructor() { + super(undefined, action, options); + } + + override render(container: HTMLElement): void { + super.render(container); + container.classList.toggle('command-center-quick-pick'); + + const action = this.action; + + // icon (search) + const searchIcon = document.createElement('span'); + searchIcon.className = action.class ?? ''; + searchIcon.classList.add('search-icon'); + + // label: just workspace name and optional decorations + const label = this._getLabel(); + const labelElement = document.createElement('span'); + labelElement.classList.add('search-label'); + labelElement.innerText = label; + reset(container, searchIcon, labelElement); + + const hover = this._store.add(setupCustomHover(that._hoverDelegate, container, this.getTooltip())); + + // update label & tooltip when window title changes + this._store.add(that._windowTitle.onDidChange(() => { + hover.update(this.getTooltip()); + labelElement.innerText = this._getLabel(); + })); + } + + protected override getTooltip() { + return that.getTooltip(); + } + + private _getLabel(): string { + const { prefix, suffix } = that._windowTitle.getTitleDecorations(); + let label = that._windowTitle.isCustomTitleFormat() ? that._windowTitle.getWindowTitle() : that._windowTitle.workspaceName; + if (!label) { + label = localize('label.dfl', "Search"); + } + if (prefix) { + label = localize('label1', "{0} {1}", prefix, label); + } + if (suffix) { + label = localize('label2', "{0} {1}", label, suffix); + } + return label; + } + }); + } + }); + toolbar.setActions(group); + this._store.add(toolbar); + + // spacer + if (i < groups.length - 1) { + const icon = renderIcon(Codicon.circleSmallFilled); + icon.classList.add('spacer'); + container.appendChild(icon); + } + } + } + + protected override getTooltip() { + + // tooltip: full windowTitle + const kb = this._keybindingService.lookupKeybinding(this.action.id)?.getLabel(); + const title = kb + ? localize('title', "Search {0} ({1}) \u2014 {2}", this._windowTitle.workspaceName, kb, this._windowTitle.value) + : localize('title2', "Search {0} \u2014 {1}", this._windowTitle.workspaceName, this._windowTitle.value); + + return title; + } +} + +MenuRegistry.appendMenuItem(MenuId.CommandCenter, { + submenu: MenuId.CommandCenterCenter, + title: localize('title3', "Command Center"), + icon: Codicon.shield, + order: 101, +}); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 315429f1f85..b0c29642ec2 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -134,19 +134,19 @@ visibility: hidden; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .codicon { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { display: flex; align-items: stretch; color: var(--vscode-commandCenter-foreground); @@ -163,27 +163,37 @@ max-width: 600px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center:only-child { - margin-left: 0; /* no margin if there is only the command center, without nav buttons */ +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick { + display: flex; } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center { - color: var(--vscode-titleBar-inactiveForeground); - border-color: var(--vscode-commandCenter-inactiveBorder) !important; -} - -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center .search-icon { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-icon { font-size: 14px; opacity: .8; margin: auto 3px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center .search-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center .action-item.command-center-quick-pick .search-label { overflow: hidden; text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center:HOVER { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple .spacer { + height: 100%; + padding: 0 6px; + opacity: 0.5; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:only-child { + margin-left: 0; /* no margin if there is only the command center, without nav buttons */ +} + +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center { + color: var(--vscode-titleBar-inactiveForeground); + border-color: var(--vscode-commandCenter-inactiveBorder) !important; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:HOVER { color: var(--vscode-commandCenter-activeForeground); background-color: var(--vscode-commandCenter-activeBackground); border-color: var(--vscode-commandCenter-activeBorder); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index ec47bd48bca..552e59959ab 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/window/common/window'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { IAction, Action, SubmenuAction, Separator, IActionRunner, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, toAction } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform'; @@ -331,14 +331,15 @@ export abstract class MenubarControl extends Disposable { openable = { fileUri: uri }; } - const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, event => { - const browserEvent = event as KeyboardEvent; - const openInNewWindow = event && ((!isMacintosh && (browserEvent.ctrlKey || browserEvent.shiftKey)) || (isMacintosh && (browserEvent.metaKey || browserEvent.altKey))); + const ret = toAction({ + id: commandId, label: unmnemonicLabel(label), run: (browserEvent: KeyboardEvent) => { + const openInNewWindow = browserEvent && ((!isMacintosh && (browserEvent.ctrlKey || browserEvent.shiftKey)) || (isMacintosh && (browserEvent.metaKey || browserEvent.altKey))); - return this.hostService.openWindow([openable], { - forceNewWindow: !!openInNewWindow, - remoteAuthority: remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable - }); + return this.hostService.openWindow([openable], { + forceNewWindow: !!openInNewWindow, + remoteAuthority: remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable + }); + } }); return Object.assign(ret, { uri, remoteAuthority }); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 22fa7481b12..b15e2930425 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,7 +19,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from 'vs/base/common/platform'; import { Color } from 'vs/base/common/color'; -import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, prepend, reset } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -77,7 +77,6 @@ export class TitlebarPart extends Part implements ITitleService { private appIconBadge: HTMLElement | undefined; protected menubar?: HTMLElement; protected layoutControls: HTMLElement | undefined; - private layoutToolbar: MenuWorkbenchToolBar | undefined; protected lastLayoutDimensions: Dimension | undefined; private hoverDelegate: IHoverDelegate; @@ -273,18 +272,17 @@ export class TitlebarPart extends Part implements ITitleService { this.title = append(this.centerContent, $('div.window-title')); this.updateTitle(); - if (this.titleBarStyle !== 'native') { this.layoutControls = append(this.rightContent, $('div.layout-controls-container')); this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled); - this.layoutToolbar = this.instantiationService.createInstance(MenuWorkbenchToolBar, this.layoutControls, MenuId.LayoutControlMenu, { + this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, this.layoutControls, MenuId.LayoutControlMenu, { contextMenu: MenuId.TitleBarContext, toolbarOptions: { primaryGroup: () => true }, actionViewItemProvider: action => { return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); } - }); + })); } let primaryControlLocation = isMacintosh ? 'left' : 'right'; @@ -310,29 +308,6 @@ export class TitlebarPart extends Part implements ITitleService { })); }); - // Since the title area is used to drag the window, we do not want to steal focus from the - // currently active element. So we restore focus after a timeout back to where it was. - this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => { - if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) { - return; - } - - if (e.target && this.layoutToolbar && isAncestor(e.target as HTMLElement, this.layoutToolbar.getElement())) { - return; - } - - if (e.target && isAncestor(e.target as HTMLElement, this.title)) { - return; - } - - const active = document.activeElement; - setTimeout(() => { - if (active instanceof HTMLElement) { - active.focus(); - } - }, 0 /* need a timeout because we are in capture phase */); - }, true /* use capture to know the currently active element properly */)); - this.updateStyles(); const that = this; @@ -347,7 +322,7 @@ export class TitlebarPart extends Part implements ITitleService { }); } - run(accessor: ServicesAccessor, ...args: any[]): void { + run(): void { if (that.customMenubar) { that.customMenubar.toggleFocus(); } else { diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts index 7c4ff4fa763..afc3f54369b 100644 --- a/src/vs/workbench/browser/quickaccess.ts +++ b/src/vs/workbench/browser/quickaccess.ts @@ -24,6 +24,7 @@ export interface IWorkbenchQuickAccessConfiguration { readonly experimental: { readonly suggestCommands: boolean; readonly enableNaturalLanguageSearch: boolean; + readonly askChatLocation: 'quickChat' | 'chatView'; }; }; readonly quickOpen: { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index b464fe5abac..580e3ac1026 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -315,7 +315,7 @@ export class BrowserMain extends Disposable { serviceCollection.set(IUserDataProfilesService, userDataProfilesService); const currentProfile = await this.getCurrentProfile(workspace, userDataProfilesService, environmentService); - const userDataProfileService = new UserDataProfileService(currentProfile, userDataProfilesService); + const userDataProfileService = new UserDataProfileService(currentProfile); serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 4a5503dd6be..83aa1e53241 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -387,6 +387,17 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('suggestCommands', "Controls whether the command palette should have a list of commonly used commands."), 'default': false }, + 'workbench.commandPalette.experimental.askChatLocation': { + 'type': 'string', + tags: ['experimental'], + 'description': localize('askChatLocation', "Controls where the command palette should ask chat questions."), + 'default': 'chatView', + enum: ['chatView', 'quickChat'], + enumDescriptions: [ + localize('askChatLocation.chatView', "Ask chat questions in the Chat view."), + localize('askChatLocation.quickChat', "Ask chat questions in Quick Chat.") + ] + }, 'workbench.commandPalette.experimental.enableNaturalLanguageSearch': { 'type': 'boolean', tags: ['experimental'], diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index a4a46546750..9d31beaefc2 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -875,6 +875,59 @@ export const MENUBAR_SELECTION_BORDER = registerColor('menubar.selectionBorder', hcLight: activeContrastBorder, }, localize('menubarSelectionBorder', "Border color of the selected menu item in the menubar.")); +// < --- Command Center --- > + +// foreground (inactive and active) +export const COMMAND_CENTER_FOREGROUND = registerColor( + 'commandCenter.foreground', + { dark: TITLE_BAR_ACTIVE_FOREGROUND, hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: TITLE_BAR_ACTIVE_FOREGROUND, hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, + localize('commandCenter-foreground', "Foreground color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEFOREGROUND = registerColor( + 'commandCenter.activeForeground', + { dark: MENUBAR_SELECTION_FOREGROUND, hcDark: MENUBAR_SELECTION_FOREGROUND, light: MENUBAR_SELECTION_FOREGROUND, hcLight: MENUBAR_SELECTION_FOREGROUND }, + localize('commandCenter-activeForeground', "Active foreground color of the command center"), + false +); +export const COMMAND_CENTER_INACTIVEFOREGROUND = registerColor( + 'commandCenter.inactiveForeground', + { dark: TITLE_BAR_INACTIVE_FOREGROUND, hcDark: TITLE_BAR_INACTIVE_FOREGROUND, light: TITLE_BAR_INACTIVE_FOREGROUND, hcLight: TITLE_BAR_INACTIVE_FOREGROUND }, + localize('commandCenter-inactiveForeground', "Foreground color of the command center when the window is inactive"), + false +); +// background (inactive and active) +export const COMMAND_CENTER_BACKGROUND = registerColor( + 'commandCenter.background', + { dark: Color.white.transparent(0.05), hcDark: null, light: Color.black.transparent(0.05), hcLight: null }, + localize('commandCenter-background', "Background color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEBACKGROUND = registerColor( + 'commandCenter.activeBackground', + { dark: Color.white.transparent(0.08), hcDark: MENUBAR_SELECTION_BACKGROUND, light: Color.black.transparent(0.08), hcLight: MENUBAR_SELECTION_BACKGROUND }, + localize('commandCenter-activeBackground', "Active background color of the command center"), + false +); +// border: active and inactive. defaults to active background +export const COMMAND_CENTER_BORDER = registerColor( + 'commandCenter.border', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60), light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60) }, + localize('commandCenter-border', "Border color of the command center"), + false +); +export const COMMAND_CENTER_ACTIVEBORDER = registerColor( + 'commandCenter.activeBorder', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcDark: TITLE_BAR_ACTIVE_FOREGROUND, light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .30), hcLight: TITLE_BAR_ACTIVE_FOREGROUND }, + localize('commandCenter-activeBorder', "Active border color of the command center"), + false +); +// border: defaults to active background +export const COMMAND_CENTER_INACTIVEBORDER = registerColor( + 'commandCenter.inactiveBorder', { dark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcDark: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), light: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25), hcLight: transparent(TITLE_BAR_INACTIVE_FOREGROUND, .25) }, + localize('commandCenter-inactiveBorder', "Border color of the command center when the window is inactive"), + false +); + + // < --- Notifications --- > export const NOTIFICATIONS_CENTER_BORDER = registerColor('notificationCenter.border', { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 4725fbf0d9d..de7ddcd7c0c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -11,6 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution'; import { EditorAccessibilityHelpContribution, HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; +import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); @@ -23,3 +24,4 @@ const workbenchContributionsRegistry = Registry.as('accessibleViewI export const accessibleViewSupportsNavigation = new RawContextKey('accessibleViewSupportsNavigation', false, true); export const accessibleViewVerbosityEnabled = new RawContextKey('accessibleViewVerbosityEnabled', false, true); export const accessibleViewGoToSymbolSupported = new RawContextKey('accessibleViewGoToSymbolSupported', false, true); +export const accessibleViewOnLastLine = new RawContextKey('accessibleViewOnLastLine', false, true); export const accessibleViewCurrentProviderId = new RawContextKey('accessibleViewCurrentProviderId', undefined, undefined); /** diff --git a/src/vs/workbench/browser/parts/editor/accessibilityStatus.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts similarity index 100% rename from src/vs/workbench/browser/parts/editor/accessibilityStatus.ts rename to src/vs/workbench/contrib/accessibility/browser/accessibilityStatus.ts diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 1f82f5c1e9b..bbde0f86f2c 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -18,6 +18,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; +import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; @@ -36,7 +37,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -73,6 +74,9 @@ export interface IAccessibleViewService { previous(): void; goToSymbol(): void; disableHint(): void; + getPosition(): Position | undefined; + setPosition(position: Position, reveal?: boolean): void; + getLastPosition(): Position | undefined; /** * If the setting is enabled, provides the open accessible view hint as a localized string. * @param verbositySettingKey The setting key for the verbosity of the feature @@ -85,6 +89,11 @@ export const enum AccessibleViewType { View = 'view' } +export const enum NavigationType { + Previous = 'previous', + Next = 'next' +} + export interface IAccessibleViewOptions { readMoreUrl?: string; /** @@ -94,10 +103,11 @@ export interface IAccessibleViewOptions { type: AccessibleViewType; } -class AccessibleView extends Disposable { +export class AccessibleView extends Disposable { private _editorWidget: CodeEditorWidget; private _accessiblityHelpIsShown: IContextKey; + private _onLastLine: IContextKey; private _accessibleViewIsShown: IContextKey; private _accessibleViewSupportsNavigation: IContextKey; private _accessibleViewVerbosityEnabled: IContextKey; @@ -132,6 +142,7 @@ class AccessibleView extends Disposable { this._accessibleViewVerbosityEnabled = accessibleViewVerbosityEnabled.bindTo(this._contextKeyService); this._accessibleViewGoToSymbolSupported = accessibleViewGoToSymbolSupported.bindTo(this._contextKeyService); this._accessibleViewCurrentProviderId = accessibleViewCurrentProviderId.bindTo(this._contextKeyService); + this._onLastLine = accessibleViewOnLastLine.bindTo(this._contextKeyService); this._container = document.createElement('div'); this._container.classList.add('accessible-view'); @@ -182,6 +193,9 @@ class AccessibleView extends Disposable { } })); this._register(this._editorWidget.onDidDispose(() => this._resetContextKeys())); + this._register(this._editorWidget.onDidChangeCursorPosition(() => { + this._onLastLine.set(this._editorWidget.getPosition()?.lineNumber === this._editorWidget.getModel()?.getLineCount()); + })); } private _resetContextKeys(): void { @@ -242,11 +256,23 @@ class AccessibleView extends Disposable { if (!this._currentProvider || !this._currentContent) { return; } - const tokens = this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown' ? this._currentProvider.getSymbols?.() : marked.lexer(this._currentContent); - if (!tokens) { + const symbols: IAccessibleViewSymbol[] = this._currentProvider.getSymbols?.() || []; + if (symbols?.length) { + return symbols; + } + if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') { + // Symbols haven't been provided and we cannot parse this language return; } - const symbols: IAccessibleViewSymbol[] = []; + const markdownTokens: marked.TokensList | undefined = marked.lexer(this._currentContent); + if (!markdownTokens) { + return; + } + this._convertTokensToSymbols(markdownTokens, symbols); + return symbols.length ? symbols : undefined; + } + + private _convertTokensToSymbols(tokens: marked.TokensList, symbols: IAccessibleViewSymbol[]): void { let firstListItem: string | undefined; for (const token of tokens) { let label: string | undefined = undefined; @@ -267,27 +293,39 @@ class AccessibleView extends Disposable { break; } } - } else { - label = token.label; } if (label) { - symbols.push({ info: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label), firstListItem }); + symbols.push({ markdownToParse: label, label: localize('symbolLabel', "({0}) {1}", token.type, label), ariaLabel: localize('symbolLabelAria', "({0}) {1}", token.type, label), firstListItem }); firstListItem = undefined; } } - return symbols; } showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void { if (!this._currentContent) { return; } - const index = this._currentContent.split('\n').findIndex(line => line.includes(symbol.info.split('\n')[0]) || (symbol.firstListItem && line.includes(symbol.firstListItem))) ?? -1; - if (index >= 0) { - this.show(provider); - this._editorWidget.revealLine(index + 1); - this._editorWidget.setSelection({ startLineNumber: index + 1, startColumn: 1, endLineNumber: index + 1, endColumn: 1 }); + let lineNumber: number | undefined = symbol.lineNumber; + const markdownToParse = symbol.markdownToParse; + if (lineNumber === undefined && markdownToParse === undefined) { + // No symbols provided and we cannot parse this language + return; } + + if (lineNumber === undefined && markdownToParse) { + // Note that this scales poorly, thus isn't used for worst case scenarios like the terminal, for which a line number will always be provided. + // Parse the markdown to find the line number + const index = this._currentContent.split('\n').findIndex(line => line.includes(markdownToParse.split('\n')[0]) || (symbol.firstListItem && line.includes(symbol.firstListItem))) ?? -1; + if (index >= 0) { + lineNumber = index + 1; + } + } + if (lineNumber === undefined) { + return; + } + this.show(provider); + this._editorWidget.revealLine(lineNumber); + this._editorWidget.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }); this._updateContextKeys(provider, true); } @@ -345,7 +383,7 @@ class AccessibleView extends Disposable { message += '\n'; } } - this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', '\nExit this dialog via the Escape key.'); + this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint; this._updateContextKeys(provider, true); this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => { @@ -449,7 +487,7 @@ class AccessibleView extends Disposable { if (!this._currentProvider) { return false; } - return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols; + return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols?.(); } public showAccessibleViewHelp(): void { @@ -544,7 +582,6 @@ export class AccessibleViewService extends Disposable implements IAccessibleView this._accessibleView = this._register(this._instantiationService.createInstance(AccessibleView)); } this._accessibleView.show(provider); - } next(): void { this._accessibleView?.next(); @@ -574,9 +611,22 @@ export class AccessibleViewService extends Disposable implements IAccessibleView showAccessibleViewHelp(): void { this._accessibleView?.showAccessibleViewHelp(); } + getPosition(): Position | undefined { + return this._accessibleView?.editorWidget.getPosition() ?? undefined; + } + getLastPosition(): Position | undefined { + const lastLine = this._accessibleView?.editorWidget.getModel()?.getLineCount(); + return lastLine !== undefined && lastLine > 0 ? new Position(lastLine, 1) : undefined; + } + setPosition(position: Position, reveal?: boolean): void { + const editorWidget = this._accessibleView?.editorWidget; + editorWidget?.setPosition(position); + if (reveal) { + editorWidget?.revealLine(position.lineNumber); + } + } } - class AccessibleViewSymbolQuickPick { constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) { @@ -612,7 +662,8 @@ class AccessibleViewSymbolQuickPick { } } -interface IAccessibleViewSymbol extends IPickerQuickAccessItem { - info: string; +export interface IAccessibleViewSymbol extends IPickerQuickAccessItem { + markdownToParse?: string; firstListItem?: string; + lineNumber?: number; } diff --git a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts index 0d9c1bec943..0cd7d898147 100644 --- a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts @@ -47,7 +47,7 @@ export class UnfocusedViewDimmingContribution extends Disposable implements IWor // Text editors rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .monaco-editor { ${filterRule} }`); // Breadcrumbs - rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .tabs-breadcrumbs { ${filterRule} }`); + rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .breadcrumbs-below-tabs { ${filterRule} }`); // Terminal editors rules.add(`.monaco-workbench .editor-instance:not(:focus-within) .terminal-wrapper { ${filterRule} }`); // Settings editor diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index ca3c43e92d6..3655eba1cde 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -32,8 +32,47 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; export const CHAT_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' }; +export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; + +class QuickChatGlobalAction extends Action2 { + constructor() { + super({ + id: CHAT_OPEN_ACTION_ID, + title: { value: localize('quickChat', "Quick Chat"), original: 'Quick Chat' }, + precondition: CONTEXT_PROVIDER_EXISTS, + icon: Codicon.commentDiscussion, + f1: false, + category: CHAT_CATEGORY, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, + mac: { + primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI + } + } + }); + } + + override async run(accessor: ServicesAccessor, query?: string): Promise { + const chatService = accessor.get(IChatService); + const chatWidgetService = accessor.get(IChatWidgetService); + const providers = chatService.getProviderInfos(); + if (!providers.length) { + return; + } + const chatWidget = await chatWidgetService.revealViewForProvider(providers[0].id); + if (!chatWidget) { + return; + } + if (query) { + chatWidget.acceptInput(query); + } + chatWidget.focusInput(); + } +} export function registerChatActions() { + registerAction2(QuickChatGlobalAction); registerEditorAction(class ChatAcceptInput extends EditorAction { constructor() { super({ diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 4be8c09a49f..987bcadb81f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -32,6 +32,9 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; +import { LOG_MODE_ID, OUTPUT_MODE_ID } from 'vs/workbench/services/output/common/output'; +import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search'; +import { GHOST_TEXT_DESCRIPTION } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget'; const $ = dom.$; @@ -50,22 +53,22 @@ Registry.as(Extensions.ConfigurationMigration) ]) }]); -const emptyTextEditorHintSetting = 'workbench.editor.empty.hint'; +export const emptyTextEditorHintSetting = 'workbench.editor.empty.hint'; export class EmptyTextEditorHintContribution implements IEditorContribution { public static readonly ID = 'editor.contrib.emptyTextEditorHint'; - private toDispose: IDisposable[]; + protected toDispose: IDisposable[]; private textHintContentWidget: EmptyTextEditorHintContentWidget | undefined; constructor( - private readonly editor: ICodeEditor, + protected readonly editor: ICodeEditor, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, @ICommandService private readonly commandService: ICommandService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, - @IInlineChatService private readonly inlineChatService: IInlineChatService, + @IInlineChatService protected readonly inlineChatService: IInlineChatService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IProductService private readonly productService: IProductService, ) { @@ -89,16 +92,36 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { })); } - private update(): void { - this.textHintContentWidget?.dispose(); + protected _shouldRenderHint() { const configValue = this.configurationService.getValue(emptyTextEditorHintSetting); + if (configValue === 'hidden') { + return false; + } + + if (this.editor.getOption(EditorOption.readOnly)) { + return false; + } + + const hasGhostText = this.editor.getLineDecorations(0)?.find((d) => d.options.description === GHOST_TEXT_DESCRIPTION); + if (hasGhostText) { + return false; + } + const model = this.editor.getModel(); + const languageId = model?.getLanguageId(); + if (languageId === OUTPUT_MODE_ID || languageId === LOG_MODE_ID || languageId === SEARCH_RESULT_LANGUAGE_ID) { + return false; + } const inlineChatProviders = [...this.inlineChatService.getAllProvider()]; - const shouldRenderInlineChatHint = inlineChatProviders.length > 0; - const shouldRenderDefaultHint = model?.getLanguageId() === PLAINTEXT_LANGUAGE_ID && !inlineChatProviders.length; + const shouldRenderDefaultHint = model?.uri.scheme === Schemas.untitled && languageId === PLAINTEXT_LANGUAGE_ID && !inlineChatProviders.length; + return inlineChatProviders.length > 0 || shouldRenderDefaultHint; + } - if (model && (model.uri.scheme === Schemas.untitled && shouldRenderDefaultHint || shouldRenderInlineChatHint) && configValue !== 'hidden') { + protected update(): void { + this.textHintContentWidget?.dispose(); + + if (this._shouldRenderHint()) { this.textHintContentWidget = new EmptyTextEditorHintContentWidget( this.editor, this.editorGroupsService, diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index 52ac3a4afb1..9c2cae83890 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -44,7 +44,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC 'Variable 0 will be a file name.' ] }, - "{0}: tokenization, wrapping, folding and sticky scroll have been turned off for this large file in order to reduce memory usage and avoid freezing or crashing.", + "{0}: tokenization, wrapping, folding, codelens, word highlighting and sticky scroll have been turned off for this large file in order to reduce memory usage and avoid freezing or crashing.", path.basename(model.uri.path) ); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 7c30e5d516f..4a859f036a4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -48,6 +48,7 @@ import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/comment import { FileAccess } from 'vs/base/common/network'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; class CommentsActionRunner extends ActionRunner { protected override async runAction(action: IAction, context: any[]): Promise { @@ -109,7 +110,8 @@ export class CommentNode extends Disposable { @INotificationService private notificationService: INotificationService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService, + @IAccessibilityService private accessibilityService: IAccessibilityService ) { super(); @@ -156,6 +158,9 @@ export class CommentNode extends Disposable { if (pendingEdit) { this.switchToEditMode(); } + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => { + this.toggleToolbarHidden(true); + })); } private createScroll(container: HTMLElement, body: HTMLElement) { @@ -246,10 +251,19 @@ export class CommentNode extends Disposable { this._isPendingLabel.innerText = ''; } - this._actionsToolbarContainer = dom.append(header, dom.$('.comment-actions.hidden')); + this._actionsToolbarContainer = dom.append(header, dom.$('.comment-actions')); + this.toggleToolbarHidden(true); this.createActionsToolbar(); } + private toggleToolbarHidden(hidden: boolean) { + if (hidden && !this.accessibilityService.isScreenReaderOptimized()) { + this._actionsToolbarContainer.classList.add('hidden'); + } else { + this._actionsToolbarContainer.classList.remove('hidden'); + } + } + private getToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { const contributedActions = menu.getActions({ shouldForwardArgs: true }); const primary: IAction[] = []; @@ -617,7 +631,7 @@ export class CommentNode extends Disposable { setFocus(focused: boolean, visible: boolean = false) { if (focused) { this._domNode.focus(); - this._actionsToolbarContainer.classList.remove('hidden'); + this.toggleToolbarHidden(false); this._actionsToolbarContainer.classList.add('tabfocused'); this._domNode.tabIndex = 0; if (this.comment.mode === languages.CommentMode.Editing) { @@ -625,7 +639,7 @@ export class CommentNode extends Disposable { } } else { if (this._actionsToolbarContainer.classList.contains('tabfocused') && !this._actionsToolbarContainer.classList.contains('mouseover')) { - this._actionsToolbarContainer.classList.add('hidden'); + this.toggleToolbarHidden(true); this._domNode.tabIndex = -1; } this._actionsToolbarContainer.classList.remove('tabfocused'); @@ -634,12 +648,12 @@ export class CommentNode extends Disposable { private registerActionBarListeners(actionsContainer: HTMLElement): void { this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { - actionsContainer.classList.remove('hidden'); + this.toggleToolbarHidden(false); actionsContainer.classList.add('mouseover'); })); this._register(dom.addDisposableListener(this._domNode, 'mouseleave', () => { if (actionsContainer.classList.contains('mouseover') && !actionsContainer.classList.contains('tabfocused')) { - actionsContainer.classList.add('hidden'); + this.toggleToolbarHidden(true); } actionsContainer.classList.remove('mouseover'); })); diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 6a8cc9c290c..2dbc44ada62 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions } from 'vs/editor/common/languages'; +import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -18,6 +18,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; export const ICommentService = createDecorator('commentService'); @@ -69,6 +70,10 @@ export interface ICommentController { getNotebookComments(resource: URI, token: CancellationToken): Promise; } +export interface IContinueOnCommentProvider { + provideContinueOnComments(): PendingCommentThread[]; +} + export interface ICommentService { readonly _serviceBrand: undefined; readonly onDidSetResourceCommentInfos: Event; @@ -103,8 +108,12 @@ export interface ICommentService { setActiveCommentThread(commentThread: CommentThread | null): void; setCurrentCommentThread(commentThread: CommentThread | undefined): void; enableCommenting(enable: boolean): void; + registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; + removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string }): PendingCommentThread | undefined; } +const CONTINUE_ON_COMMENTS = 'comments.continueOnComments'; + export class CommentService extends Disposable implements ICommentService { declare readonly _serviceBrand: undefined; @@ -152,16 +161,49 @@ export class CommentService extends Disposable implements ICommentService { private _isCommentingEnabled: boolean = true; private _workspaceHasCommenting: IContextKey; + private _continueOnComments = new Map(); // owner -> PendingCommentThread[] + private _continueOnCommentProviders = new Set(); + constructor( @IInstantiationService protected readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IStorageService private readonly storageService: IStorageService ) { super(); this._handleConfiguration(); this._handleZenMode(); this._workspaceHasCommenting = WorkspaceHasCommenting.bindTo(contextKeyService); + const storageListener = this._register(new DisposableStore()); + + storageListener.add(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener)((v) => { + if (!v.external) { + return; + } + const commentsToRestore: PendingCommentThread[] | undefined = this.storageService.getObject(CONTINUE_ON_COMMENTS, StorageScope.WORKSPACE); + if (!commentsToRestore) { + return; + } + const changedOwners = this._addContinueOnComments(commentsToRestore); + for (const owner of changedOwners) { + const evt: ICommentThreadChangedEvent = { + owner, + pending: this._continueOnComments.get(owner) || [], + added: [], + removed: [], + changed: [] + }; + this._onDidUpdateCommentThreads.fire(evt); + } + })); + this._register(storageService.onWillSaveState(() => { + for (const provider of this._continueOnCommentProviders) { + const pendingComments = provider.provideContinueOnComments(); + this._addContinueOnComments(pendingComments); + } + this._saveContinueOnComments(); + })); } private _handleConfiguration() { @@ -325,12 +367,17 @@ export class CommentService extends Disposable implements ICommentService { async getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]> { const commentControlResult: Promise[] = []; - this._commentControls.forEach(control => { + for (const control of this._commentControls.values()) { commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None) + .then(documentComments => { + const pendingComments = this._continueOnComments.get(documentComments.owner); + documentComments.pendingCommentThreads = pendingComments?.filter(pendingComment => pendingComment.uri.toString() === resource.toString()); + return documentComments; + }) .catch(_ => { return null; })); - }); + } return Promise.all(commentControlResult); } @@ -347,4 +394,46 @@ export class CommentService extends Disposable implements ICommentService { return Promise.all(commentControlResult); } + + registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable { + this._continueOnCommentProviders.add(provider); + return { + dispose: () => { + this._continueOnCommentProviders.delete(provider); + } + }; + } + + private _saveContinueOnComments() { + const commentsToSave: PendingCommentThread[] = []; + for (const pendingComments of this._continueOnComments.values()) { + commentsToSave.push(...pendingComments); + } + this.storageService.store(CONTINUE_ON_COMMENTS, commentsToSave, StorageScope.WORKSPACE, StorageTarget.USER); + } + + removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string }): PendingCommentThread | undefined { + const pendingComments = this._continueOnComments.get(pendingComment.owner); + if (pendingComments) { + return pendingComments.splice(pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range)), 1)[0]; + } + return undefined; + } + + private _addContinueOnComments(pendingComments: PendingCommentThread[]): Set { + const changedOwners = new Set(); + for (const pendingComment of pendingComments) { + if (!this._continueOnComments.has(pendingComment.owner)) { + this._continueOnComments.set(pendingComment.owner, [pendingComment]); + changedOwners.add(pendingComment.owner); + } else { + const commentsForOwner = this._continueOnComments.get(pendingComment.owner)!; + if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range) || (comment.body !== pendingComment.body))) { + commentsForOwner.push(pendingComment); + changedOwners.add(pendingComment.owner); + } + } + } + return changedOwners; + } } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 148fa2cdeb9..aff152a1a9c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -29,6 +29,8 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; @@ -46,6 +48,8 @@ export class CommentThreadWidget extends private _onDidResize = new Emitter(); onDidResize = this._onDidResize.event; + private _commentThreadState: languages.CommentThreadState | undefined; + get commentThread() { return this._commentThread; } @@ -55,7 +59,7 @@ export class CommentThreadWidget extends private _owner: string, private _parentResourceUri: URI, private _contextKeyService: IContextKeyService, - private _scopedInstatiationService: IInstantiationService, + private _scopedInstantiationService: IInstantiationService, private _commentThread: languages.CommentThread, private _pendingComment: string | undefined, private _pendingEdits: { [key: number]: string } | undefined, @@ -66,7 +70,8 @@ export class CommentThreadWidget extends collapse: () => void; }, @ICommentService private commentService: ICommentService, - @IContextMenuService contextMenuService: IContextMenuService + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService private configurationService: IConfigurationService ) { super(); @@ -83,7 +88,7 @@ export class CommentThreadWidget extends this._commentMenus, this._commentThread, this._contextKeyService, - this._scopedInstatiationService, + this._scopedInstantiationService, contextMenuService ); @@ -107,7 +112,7 @@ export class CommentThreadWidget extends } })); - this._body = this._scopedInstatiationService.createInstance( + this._body = this._scopedInstantiationService.createInstance( CommentThreadBody, this._owner, this._parentResourceUri, @@ -115,7 +120,7 @@ export class CommentThreadWidget extends this._markdownOptions, this._commentThread, this._pendingEdits, - this._scopedInstatiationService, + this._scopedInstantiationService, this ) as unknown as CommentThreadBody; this._register(this._body); @@ -170,6 +175,9 @@ export class CommentThreadWidget extends } updateCommentThread(commentThread: languages.CommentThread) { + const shouldCollapse = (this._commentThread.collapsibleState === languages.CommentThreadCollapsibleState.Expanded) && (this._commentThreadState === languages.CommentThreadState.Unresolved) + && (commentThread.state === languages.CommentThreadState.Resolved); + this._commentThreadState = commentThread.state; this._commentThread = commentThread; dispose(this._commentThreadDisposables); this._commentThreadDisposables = []; @@ -185,6 +193,10 @@ export class CommentThreadWidget extends } else { this._commentThreadContextValue.reset(); } + + if (shouldCollapse && this.configurationService.getValue(COMMENTS_SECTION).collapseOnResolve) { + this.collapse(); + } } display(lineHeight: number) { @@ -243,12 +255,12 @@ export class CommentThreadWidget extends } private _createCommentForm() { - this._commentReply = this._scopedInstatiationService.createInstance( + this._commentReply = this._scopedInstantiationService.createInstance( CommentReply, this._owner, this._body.container, this._commentThread, - this._scopedInstatiationService, + this._scopedInstantiationService, this._contextKeyService, this._commentMenus, this._commentOptions, @@ -261,7 +273,7 @@ export class CommentThreadWidget extends } private _createAdditionalActions() { - this._additionalActions = this._scopedInstatiationService.createInstance( + this._additionalActions = this._scopedInstantiationService.createInstance( CommentThreadAdditionalActions, this._body.container, this._commentThread, diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index bec1b69199e..63400eb834c 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -44,6 +44,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', default: true, description: nls.localize('comments.maxHeight', "Controls whether the comments widget scrolls or expands.") + }, + 'comments.collapseOnResolve': { + type: 'boolean', + default: true, + description: nls.localize('collapseOnResolve', "Controls whether the comment thread should collapse when the thread is resolved.") } } }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index de414ee854f..636de0344f6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -395,7 +395,39 @@ export class CommentController implements IEditorContribution { this.onModelChanged(); this.codeEditorService.registerDecorationType('comment-controller', COMMENTEDITOR_DECORATION_KEY, {}); - this.beginCompute(); + this.commentService.registerContinueOnCommentProvider({ + provideContinueOnComments: () => { + const pendingComments: languages.PendingCommentThread[] = []; + if (this._commentWidgets) { + for (const zone of this._commentWidgets) { + const zonePendingComments = zone.getPendingComments(); + const pendingNewComment = zonePendingComments.newComment; + if (!pendingNewComment || !zone.commentThread.range) { + continue; + } + let lastCommentBody; + if (zone.commentThread.comments && zone.commentThread.comments.length) { + const lastComment = zone.commentThread.comments[zone.commentThread.comments.length - 1]; + if (typeof lastComment.body === 'string') { + lastCommentBody = lastComment.body; + } else { + lastCommentBody = lastComment.body.value; + } + } + + if (pendingNewComment !== lastCommentBody) { + pendingComments.push({ + owner: zone.owner, + uri: zone.editor.getModel()!.uri, + range: zone.commentThread.range, + body: pendingNewComment + }); + } + } + } + return pendingComments; + } + }); } private registerEditorListeners() { @@ -671,9 +703,10 @@ export class CommentController implements IEditorContribution { return; } - const added = e.added.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); - const removed = e.removed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); - const changed = e.changed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); + const added = e.added.filter(thread => thread.resource && thread.resource === editorURI.toString()); + const removed = e.removed.filter(thread => thread.resource && thread.resource === editorURI.toString()); + const changed = e.changed.filter(thread => thread.resource && thread.resource === editorURI.toString()); + const pending = e.pending.filter(pending => pending.uri.toString() === editorURI.toString()); removed.forEach(thread => { const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''); @@ -713,12 +746,17 @@ export class CommentController implements IEditorContribution { return; } - const pendingCommentText = this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId!]; + const continueOnCommentText = (thread.range ? this.commentService.removeContinueOnComment({ owner: e.owner, uri: editorURI, range: thread.range })?.body : undefined); + const pendingCommentText = (this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId!]) + ?? continueOnCommentText; const pendingEdits = this._pendingEditsCache[e.owner] && this._pendingEditsCache[e.owner][thread.threadId!]; this.displayCommentThread(e.owner, thread, pendingCommentText, pendingEdits); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); this.tryUpdateReservedSpace(); }); + pending.forEach(thread => { + this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, Range.lift(thread.range)); + }); this._commentThreadRangeDecorator.update(this.editor, commentInfo); })); @@ -1002,6 +1040,9 @@ export class CommentController implements IEditorContribution { this.displayCommentThread(info.owner, thread, pendingComment, pendingEdits); }); + info.pendingCommentThreads?.forEach(thread => { + this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, Range.lift(thread.range)); + }); }); this._commentingRangeDecorator.update(this.editor, this._commentInfos); diff --git a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts index c996a07373b..44004fe47d7 100644 --- a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts +++ b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts @@ -8,6 +8,7 @@ export interface ICommentsConfiguration { useRelativeTime: boolean; visible: boolean; maxHeight: boolean; + collapseOnResolve: boolean; } export const COMMENTS_SECTION = 'comments'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 2aaf7c47254..fc1a0120f48 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -161,7 +161,7 @@ export class BreakpointsView extends ViewPane { } else if (element instanceof DataBreakpoint) { await this.debugService.removeDataBreakpoints(element.getId()); } else if (element instanceof InstructionBreakpoint) { - await this.debugService.removeInstructionBreakpoints(element.instructionReference); + await this.debugService.removeInstructionBreakpoints(element.instructionReference, element.offset); } }); @@ -180,7 +180,7 @@ export class BreakpointsView extends ViewPane { if (e.element instanceof InstructionBreakpoint) { const disassemblyView = await this.editorService.openEditor(DisassemblyViewInput.instance); // Focus on double click - (disassemblyView as DisassemblyView).goToAddress(e.element.instructionReference, e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2); + (disassemblyView as DisassemblyView).goToInstructionAndOffset(e.element.instructionReference, e.element.offset, e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2); } if (e.browserEvent instanceof MouseEvent && e.browserEvent.detail === 2 && e.element instanceof FunctionBreakpoint && e.element !== this.inputBoxData?.breakpoint) { // double click @@ -788,7 +788,8 @@ class InstructionBreakpointsRenderer implements IListRenderer { - // TODO: add disassembly F9 - if (editor.hasModel()) { - const debugService = accessor.get(IDebugService); + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const debugService = accessor.get(IDebugService); + + const activePane = editorService.activeEditorPane; + if (activePane instanceof DisassemblyView) { + const location = activePane.focusedAddressAndOffset; + if (location) { + const bps = debugService.getModel().getInstructionBreakpoints(); + const toRemove = bps.find(bp => bp.address === location.address); + if (toRemove) { + debugService.removeInstructionBreakpoints(toRemove.instructionReference, toRemove.offset); + } else { + debugService.addInstructionBreakpoint(location.reference, location.offset, location.address); + } + } + return; + } + + const codeEditorService = accessor.get(ICodeEditorService); + const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); + if (editor?.hasModel()) { const modelUri = editor.getModel().uri; const canSet = debugService.canSetBreakpointsIn(editor.getModel()); // Does not account for multi line selections, Set to remove multiple cursor on the same line @@ -210,7 +232,7 @@ class OpenDisassemblyViewAction extends EditorAction2 { runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): void { if (editor.hasModel()) { const editorService = accessor.get(IEditorService); - editorService.openEditor(DisassemblyViewInput.instance, { pinned: true }); + editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true }); } } } @@ -251,7 +273,7 @@ export class RunToCursorAction extends EditorAction { id: RunToCursorAction.ID, label: RunToCursorAction.LABEL, alias: 'Debug: Run to Cursor', - precondition: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), EditorContextKeys.editorTextFocus), + precondition: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS)), contextMenuOpts: { group: 'debug', order: 2, @@ -572,7 +594,7 @@ class CloseExceptionWidgetAction extends EditorAction { registerAction2(OpenDisassemblyViewAction); registerAction2(ToggleDisassemblyViewSourceCodeAction); -registerEditorAction(ToggleBreakpointAction); +registerAction2(ToggleBreakpointAction); registerEditorAction(ConditionalBreakpointAction); registerEditorAction(LogPointAction); registerEditorAction(EditBreakpointAction); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 06c4fa63988..6cf4c61e3cc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -8,37 +8,40 @@ import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { distinct, flatten } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { visit } from 'vs/base/common/json'; import { setProperty } from 'vs/base/common/jsonEdit'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; +import { assertType, isDefined } from 'vs/base/common/types'; import { Constants } from 'vs/base/common/uint'; +import { URI } from 'vs/base/common/uri'; import { CoreEditingCommands } from 'vs/editor/browser/coreCommands'; import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorOption, IEditorHoverOptions } from 'vs/editor/common/config/editorOptions'; 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 { IRange, Range } from 'vs/editor/common/core/range'; import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/core/wordHelper'; import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; -import { InlineValueContext } from 'vs/editor/common/languages'; -import { IModelDeltaDecoration, InjectedTextCursorStops, ITextModel } from 'vs/editor/common/model'; +import { InlineValue, InlineValueContext } from 'vs/editor/common/languages'; +import { IModelDeltaDecoration, ITextModel, InjectedTextCursorStops } from 'vs/editor/common/model'; import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IModelService } from 'vs/editor/common/services/model'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import * as nls from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; 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 { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { FloatingEditorClickWidget } from 'vs/workbench/browser/codeeditor'; @@ -787,3 +790,31 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.oldDecorations.clear(); } } + + +CommandsRegistry.registerCommand( + '_executeInlineValueProvider', + async ( + accessor: ServicesAccessor, + uri: URI, + iRange: IRange, + context: InlineValueContext + ): Promise => { + assertType(URI.isUri(uri)); + assertType(Range.isIRange(iRange)); + + if (!context || typeof context.frameId !== 'number' || !Range.isIRange(context.stoppedLocation)) { + throw illegalArgument('context'); + } + + const model = accessor.get(IModelService).getModel(uri); + if (!model) { + throw illegalArgument('uri'); + } + + const range = Range.lift(iRange); + const { inlineValuesProvider } = accessor.get(ILanguageFeaturesService); + const providers = inlineValuesProvider.ordered(model); + const providerResults = await Promise.all(providers.map(provider => provider.provideInlineValues(model, range, context, CancellationToken.None))); + return providerResults.flat().filter(isDefined); + }); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 7e239aaa285..1bb8acebf52 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -662,9 +662,7 @@ export class DebugService implements IDebugService { this.viewModel.setFocus(undefined, this.viewModel.focusedThread, session, false); } }, 200); - const sessionStore = new DisposableStore(); - - sessionStore.add(session.onDidChangeState(() => { + this.disposables.add(session.onDidChangeState(() => { if (session.state === State.Running && this.viewModel.focusedSession === session) { sessionRunningScheduler.schedule(); } @@ -673,7 +671,7 @@ export class DebugService implements IDebugService { } })); - sessionStore.add(session.onDidEndAdapter(async adapterExitEvent => { + this.disposables.add(session.onDidEndAdapter(async adapterExitEvent => { if (adapterExitEvent) { if (adapterExitEvent.error) { @@ -726,7 +724,6 @@ export class DebugService implements IDebugService { } this.model.removeExceptionBreakpointsForSession(session.getId()); - sessionStore.dispose(); // session.dispose(); TODO@roblourens })); } @@ -1052,15 +1049,15 @@ export class DebugService implements IDebugService { await this.sendDataBreakpoints(); } - async addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise { - this.model.addInstructionBreakpoint(address, offset, condition, hitCondition); + async addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { + this.model.addInstructionBreakpoint(instructionReference, offset, address, condition, hitCondition); this.debugStorage.storeBreakpoints(this.model); await this.sendInstructionBreakpoints(); this.debugStorage.storeBreakpoints(this.model); } - async removeInstructionBreakpoints(address?: string): Promise { - this.model.removeInstructionBreakpoints(address); + async removeInstructionBreakpoints(instructionReference?: string, offset?: number): Promise { + this.model.removeInstructionBreakpoints(instructionReference, offset); this.debugStorage.storeBreakpoints(this.model); await this.sendInstructionBreakpoints(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index d95dc1fbf6e..01c4ef4ce44 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -10,7 +10,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import * as resources from 'vs/base/common/resources'; @@ -103,11 +103,14 @@ export class DebugSession implements IDebugSession, IDisposable { this.repl = (this.parentSession as DebugSession).repl; } - const toDispose = this.rawListeners.add(new DisposableStore()); + const toDispose = new DisposableStore(); const replListener = toDispose.add(new MutableDisposable()); replListener.value = this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); if (lifecycleService) { - toDispose.add(lifecycleService.onWillShutdown(() => this.shutdown())); + toDispose.add(lifecycleService.onWillShutdown(() => { + this.shutdown(); + dispose(toDispose); + })); } const compoundRoot = this._options.compoundRoot; @@ -536,7 +539,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.raw.readyForBreakpoints) { - const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints }); + const response = await this.raw.setInstructionBreakpoints({ breakpoints: instructionBreakpoints.map(ib => ib.toJSON()) }); if (response && response.body) { const data = new Map(); for (let i = 0; i < instructionBreakpoints.length; i++) { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 7e4ee4af327..7c720d874d3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -37,6 +37,7 @@ import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_ import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Codicon } from 'vs/base/common/codicons'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -102,8 +103,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const toolBarLocation = this.configurationService.getValue('debug').toolBarLocation; if ( state === State.Inactive || - toolBarLocation === 'docked' || - toolBarLocation === 'hidden' || + toolBarLocation !== 'floating' || this.debugService.getModel().getSessions().every(s => s.suppressDebugToolbar) || (state === State.Initializing && this.debugService.initializingOptions?.suppressDebugToolbar) ) { @@ -335,6 +335,15 @@ MenuRegistry.onDidChangeMenu(e => { } }); + +MenuRegistry.appendMenuItem(MenuId.CommandCenterCenter, { + submenu: MenuId.DebugToolBar, + title: 'Debug', + icon: Codicon.debug, + order: 1, + when: ContextKeyExpr.and(CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'commandCenter')) +}); + registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), undefined, { id: DISCONNECT_ID, title: DISCONNECT_LABEL, icon: icons.debugDisconnect, precondition: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED), }); diff --git a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts index 1112a89f08a..53996d57321 100644 --- a/src/vs/workbench/contrib/debug/browser/disassemblyView.ts +++ b/src/vs/workbench/contrib/debug/browser/disassemblyView.ts @@ -4,63 +4,77 @@ *--------------------------------------------------------------------------------------------*/ import { PixelRatio } from 'vs/base/browser/browser'; -import { Dimension, append, $, addStandardDisposableListener } from 'vs/base/browser/dom'; +import { $, Dimension, addStandardDisposableListener, append } from 'vs/base/browser/dom'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; +import { binarySearch2 } from 'vs/base/common/arrays'; +import { Color } from 'vs/base/common/color'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { isAbsolute } from 'vs/base/common/path'; +import { Constants } from 'vs/base/common/uint'; +import { URI } from 'vs/base/common/uri'; +import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; +import { ITextModel } from 'vs/editor/common/model'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchTable } from 'vs/platform/list/browser/listService'; +import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, DISASSEMBLY_VIEW_ID, IDebugService, IDebugSession, IInstructionBreakpoint, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; -import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; -import { topStackFrameColor, focusedStackFrameColor } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; -import { Color } from 'vs/base/common/color'; -import { InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ITextModel } from 'vs/editor/common/model'; -import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { URI } from 'vs/base/common/uri'; -import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { isAbsolute } from 'vs/base/common/path'; -import { Constants } from 'vs/base/common/uint'; -import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { binarySearch2 } from 'vs/base/common/arrays'; -import { ILogService } from 'vs/platform/log/common/log'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { focusedStackFrameColor, topStackFrameColor } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; +import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST, DISASSEMBLY_VIEW_ID, IDebugConfiguration, IDebugService, IDebugSession, IInstructionBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; +import { InstructionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { getUriFromSource } from 'vs/workbench/contrib/debug/common/debugSource'; +import { isUri, sourcesEqual } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; interface IDisassembledInstructionEntry { allowBreakpoint: boolean; isBreakpointSet: boolean; isBreakpointEnabled: boolean; + /** Instruction reference from the DA */ + instructionReference: string; + /** Offset from the instructionReference that's the basis for the `instructionOffset` */ + instructionReferenceOffset: number; + /** The number of instructions (+/-) away from the instructionReference and instructionReferenceOffset this instruction lies */ + instructionOffset: number; + /** Whether this is the first instruction on the target line. */ + showSourceLocation?: boolean; + /** Original instruction from the debugger */ instruction: DebugProtocol.DisassembledInstruction; - instructionAddress?: bigint; + /** Parsed instruction address */ + address: bigint; } + // Special entry as a placeholer when disassembly is not available const disassemblyNotAvailable: IDisassembledInstructionEntry = { allowBreakpoint: false, isBreakpointSet: false, isBreakpointEnabled: false, + instructionReference: '', + instructionOffset: 0, + instructionReferenceOffset: 0, + address: 0n, instruction: { address: '-1', instruction: localize('instructionNotAvailable', "Disassembly not available.") }, - instructionAddress: BigInt(-1) }; export class DisassemblyView extends EditorPane { @@ -75,6 +89,7 @@ export class DisassemblyView extends EditorPane { private _instructionBpList: readonly IInstructionBreakpoint[] = []; private _enableSourceCodeRender: boolean = true; private _loadingLock: boolean = false; + private readonly _referenceToMemoryAddress = new Map(); constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -87,7 +102,7 @@ export class DisassemblyView extends EditorPane { super(DISASSEMBLY_VIEW_ID, telemetryService, themeService, storageService); this._disassembledInstructions = undefined; - this._onDidChangeStackFrame = new Emitter(); + this._onDidChangeStackFrame = this._register(new Emitter({ leakWarningThreshold: 1000 })); this._previousDebuggingState = _debugService.state; this._fontInfo = BareFontInfo.createFromRawSettings(_configurationService.getValue('editor'), PixelRatio.value); this._register(_configurationService.onDidChangeConfiguration(e => { @@ -100,7 +115,7 @@ export class DisassemblyView extends EditorPane { const newValue = this._configurationService.getValue('debug').disassemblyView.showSourceCode; if (this._enableSourceCodeRender !== newValue) { this._enableSourceCodeRender = newValue; - this.reloadDisassembly(undefined); + // todo: trigger rerender } else { this._disassembledInstructions?.rerender(); } @@ -115,18 +130,29 @@ export class DisassemblyView extends EditorPane { map(session => session.getAllThreads()). reduce((prev, curr) => prev.concat(curr), []). map(thread => thread.getTopStackFrame()). - map(frame => frame?.instructionPointerReference); + map(frame => frame?.instructionPointerReference). + map(ref => ref ? this.getReferenceAddress(ref) : undefined); } - // Instruction address of the top stack frame of the focused stack - get focusedCurrentInstructionAddress() { + // Instruction reference of the top stack frame of the focused stack + get focusedCurrentInstructionReference() { return this._debugService.getViewModel().focusedStackFrame?.thread.getTopStackFrame()?.instructionPointerReference; } - get focusedInstructionAddress() { + get focusedCurrentInstructionAddress() { + const ref = this.focusedCurrentInstructionReference; + return ref ? this.getReferenceAddress(ref) : undefined; + } + + get focusedInstructionReference() { return this._debugService.getViewModel().focusedStackFrame?.instructionPointerReference; } + get focusedInstructionAddress() { + const ref = this.focusedInstructionReference; + return ref ? this.getReferenceAddress(ref) : undefined; + } + get isSourceCodeRender() { return this._enableSourceCodeRender; } get debugSession(): IDebugSession | undefined { @@ -135,6 +161,17 @@ export class DisassemblyView extends EditorPane { get onDidChangeStackFrame() { return this._onDidChangeStackFrame.event; } + get focusedAddressAndOffset() { + const element = this._disassembledInstructions?.getFocusedElements()[0]; + if (!element) { + return undefined; + } + + const reference = element.instructionReference; + const offset = Number(element.address - this.getReferenceAddress(reference)!); + return { reference, offset, address: element.address }; + } + protected createEditor(parent: HTMLElement): void { this._enableSourceCodeRender = this._configurationService.getValue('debug').disassemblyView.showSourceCode; const lineHeight = this.fontInfo.lineHeight; @@ -142,7 +179,7 @@ export class DisassemblyView extends EditorPane { const delegate = new class implements ITableVirtualDelegate{ headerRowHeight: number = 0; // No header getHeight(row: IDisassembledInstructionEntry): number { - if (thisOM.isSourceCodeRender && row.instruction.location?.path && row.instruction.line) { + if (thisOM.isSourceCodeRender && row.showSourceLocation && row.instruction.location?.path && row.instruction.line) { // instruction line + source lines if (row.instruction.endLine) { return lineHeight * (row.instruction.endLine - row.instruction.line + 2); @@ -197,7 +234,9 @@ export class DisassemblyView extends EditorPane { } )) as WorkbenchTable; - this.reloadDisassembly(); + if (this.focusedInstructionReference) { + this.reloadDisassembly(this.focusedInstructionReference, 0); + } this._register(this._disassembledInstructions.onDidScroll(e => { if (this._loadingLock) { @@ -206,10 +245,10 @@ export class DisassemblyView extends EditorPane { if (e.oldScrollTop > e.scrollTop && e.scrollTop < e.height) { this._loadingLock = true; - const topElement = Math.floor(e.scrollTop / this.fontInfo.lineHeight) + DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD; - this.scrollUp_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD).then((success) => { - if (success) { - this._disassembledInstructions!.reveal(topElement, 0); + const prevTop = Math.floor(e.scrollTop / this.fontInfo.lineHeight); + this.scrollUp_LoadDisassembledInstructions(DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD).then((loaded) => { + if (loaded > 0) { + this._disassembledInstructions!.reveal(prevTop + loaded, 0); } this._loadingLock = false; }); @@ -219,11 +258,11 @@ export class DisassemblyView extends EditorPane { } })); - this._register(this._debugService.getViewModel().onDidFocusStackFrame((stackFrame) => { - if (this._disassembledInstructions) { - this.goToAddress(); - this._onDidChangeStackFrame.fire(); + this._register(this._debugService.getViewModel().onDidFocusStackFrame(({ stackFrame }) => { + if (this._disassembledInstructions && stackFrame?.instructionPointerReference) { + this.goToInstructionAndOffset(stackFrame.instructionPointerReference, 0); } + this._onDidChangeStackFrame.fire(); })); // refresh breakpoints view @@ -233,7 +272,7 @@ export class DisassemblyView extends EditorPane { let changed = false; bpEvent.added?.forEach((bp) => { if (bp instanceof InstructionBreakpoint) { - const index = this.getIndexFromAddress(bp.instructionReference); + const index = this.getIndexFromReferenceAndOffset(bp.instructionReference, bp.offset); if (index >= 0) { this._disassembledInstructions!.row(index).isBreakpointSet = true; this._disassembledInstructions!.row(index).isBreakpointEnabled = bp.enabled; @@ -244,7 +283,7 @@ export class DisassemblyView extends EditorPane { bpEvent.removed?.forEach((bp) => { if (bp instanceof InstructionBreakpoint) { - const index = this.getIndexFromAddress(bp.instructionReference); + const index = this.getIndexFromReferenceAndOffset(bp.instructionReference, bp.offset); if (index >= 0) { this._disassembledInstructions!.row(index).isBreakpointSet = false; changed = true; @@ -254,7 +293,7 @@ export class DisassemblyView extends EditorPane { bpEvent.changed?.forEach((bp) => { if (bp instanceof InstructionBreakpoint) { - const index = this.getIndexFromAddress(bp.instructionReference); + const index = this.getIndexFromReferenceAndOffset(bp.instructionReference, bp.offset); if (index >= 0) { if (this._disassembledInstructions!.row(index).isBreakpointEnabled !== bp.enabled) { this._disassembledInstructions!.row(index).isBreakpointEnabled = bp.enabled; @@ -267,6 +306,13 @@ export class DisassemblyView extends EditorPane { // get an updated list so that items beyond the current range would render when reached. this._instructionBpList = this._debugService.getModel().getInstructionBreakpoints(); + // breakpoints restored from a previous session can be based on memory + // references that may no longer exist in the current session. Request + // those instructions to be loaded so the BP can be displayed. + for (const bp of this._instructionBpList) { + this.primeMemoryReference(bp.instructionReference); + } + if (changed) { this._onDidChangeStackFrame.fire(); } @@ -277,10 +323,12 @@ export class DisassemblyView extends EditorPane { if ((e === State.Running || e === State.Stopped) && (this._previousDebuggingState !== State.Running && this._previousDebuggingState !== State.Stopped)) { // Just started debugging, clear the view - this._disassembledInstructions?.splice(0, this._disassembledInstructions.length, [disassemblyNotAvailable]); + this.clear(); this._enableSourceCodeRender = this._configurationService.getValue('debug').disassemblyView.showSourceCode; } + this._previousDebuggingState = e; + this._onDidChangeStackFrame.fire(); })); } @@ -288,19 +336,33 @@ export class DisassemblyView extends EditorPane { this._disassembledInstructions?.layout(dimension.height); } + async goToInstructionAndOffset(instructionReference: string, offset: number, focus?: boolean) { + let addr = this._referenceToMemoryAddress.get(instructionReference); + if (addr === undefined) { + await this.loadDisassembledInstructions(instructionReference, 0, -DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD, DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD); + addr = this._referenceToMemoryAddress.get(instructionReference); + } + + if (addr) { + this.goToAddress(addr + BigInt(offset), focus); + } + } + + /** Gets the address associated with the instruction reference. */ + getReferenceAddress(instructionReference: string) { + return this._referenceToMemoryAddress.get(instructionReference); + } + /** - * Go to the address provided. If no address is provided, reveal the address of the currently focused stack frame. + * Go to the address provided. If no address is provided, reveal the address of the currently focused stack frame. Returns false if that address is not available. */ - goToAddress(address?: string, focus?: boolean): void { + private goToAddress(address: bigint, focus?: boolean): boolean { if (!this._disassembledInstructions) { - return; + return false; } if (!address) { - address = this.focusedInstructionAddress; - } - if (!address) { - return; + return false; } const index = this.getIndexFromAddress(address); @@ -311,51 +373,82 @@ export class DisassemblyView extends EditorPane { this._disassembledInstructions.domFocus(); this._disassembledInstructions.setFocus([index]); } - } else if (this._debugService.state === State.Stopped) { - // Address is not provided or not in the table currently, clear the table - // and reload if we are in the state where we can load disassembly. - this.reloadDisassembly(address); - } - } - - private async scrollUp_LoadDisassembledInstructions(instructionCount: number): Promise { - if (this._disassembledInstructions && this._disassembledInstructions.length > 0) { - const address: string | undefined = this._disassembledInstructions?.row(0).instruction.address; - return this.loadDisassembledInstructions(address, -instructionCount, instructionCount); + return true; } return false; } - private async scrollDown_LoadDisassembledInstructions(instructionCount: number): Promise { - if (this._disassembledInstructions && this._disassembledInstructions.length > 0) { - const address: string | undefined = this._disassembledInstructions?.row(this._disassembledInstructions?.length - 1).instruction.address; - return this.loadDisassembledInstructions(address, 1, instructionCount); + private async scrollUp_LoadDisassembledInstructions(instructionCount: number): Promise { + const first = this._disassembledInstructions?.row(0); + if (first) { + return this.loadDisassembledInstructions( + first.instructionReference, + first.instructionReferenceOffset, + first.instructionOffset - instructionCount, + instructionCount, + ); + } + + return 0; + } + + private async scrollDown_LoadDisassembledInstructions(instructionCount: number): Promise { + const last = this._disassembledInstructions?.row(this._disassembledInstructions?.length - 1); + if (last) { + return this.loadDisassembledInstructions( + last.instructionReference, + last.instructionReferenceOffset, + last.instructionOffset + 1, + instructionCount, + ); + } + + return 0; + } + + /** + * Sets the memory reference address. We don't just loadDisassembledInstructions + * for this, since we can't really deal with discontiguous ranges (we can't + * detect _if_ a range is discontiguous since we don't know how much memory + * comes between instructions.) + */ + private async primeMemoryReference(instructionReference: string) { + if (this._referenceToMemoryAddress.has(instructionReference)) { + return true; + } + + const s = await this.debugSession?.disassemble(instructionReference, 0, 0, 1); + if (s && s.length > 0) { + try { + this._referenceToMemoryAddress.set(instructionReference, BigInt(s[0].address)); + return true; + } catch { + return false; + } } return false; } - private async loadDisassembledInstructions(address: string | undefined, instructionOffset: number, instructionCount: number): Promise { - // if address is null, then use current stack frame. - if (!address || address === '-1') { - address = this.focusedInstructionAddress; - } - if (!address) { - return false; - } - - // console.log(`DisassemblyView: loadDisassembledInstructions ${address}, ${instructionOffset}, ${instructionCount}`); + /** Loads disasembled instructions. Returns the number of instructions that were loaded. */ + private async loadDisassembledInstructions(instructionReference: string, offset: number, instructionOffset: number, instructionCount: number): Promise { const session = this.debugSession; - const resultEntries = await session?.disassemble(address, 0, instructionOffset, instructionCount); + const resultEntries = await session?.disassemble(instructionReference, offset, instructionOffset, instructionCount); + + // Ensure we always load the baseline instructions so we know what address the instructionReference refers to. + if (!this._referenceToMemoryAddress.has(instructionReference) && instructionOffset !== 0) { + await this.loadDisassembledInstructions(instructionReference, 0, 0, DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD); + } + if (session && resultEntries && this._disassembledInstructions) { const newEntries: IDisassembledInstructionEntry[] = []; let lastLocation: DebugProtocol.Source | undefined; let lastLine: IRange | undefined; for (let i = 0; i < resultEntries.length; i++) { - const found = this._instructionBpList.find(p => p.instructionReference === resultEntries[i].address); const instruction = resultEntries[i]; + const thisInstructionOffset = instructionOffset + i; // Forward fill the missing location as detailed in the DAP spec. if (instruction.location) { @@ -378,76 +471,152 @@ export class DisassemblyView extends EditorPane { } } - newEntries.push({ allowBreakpoint: true, isBreakpointSet: found !== undefined, isBreakpointEnabled: !!found?.enabled, instruction: instruction }); + let address: bigint; + try { + address = BigInt(instruction.address); + } catch { + console.error(`Could not parse disassembly address ${instruction.address} (in ${JSON.stringify(instruction)})`); + continue; + } + + const entry: IDisassembledInstructionEntry = { + allowBreakpoint: true, + isBreakpointSet: false, + isBreakpointEnabled: false, + instructionReference, + instructionReferenceOffset: offset, + instructionOffset: thisInstructionOffset, + instruction, + address, + }; + + newEntries.push(entry); + + // if we just loaded the first instruction for this reference, mark its address. + if (offset === 0 && thisInstructionOffset === 0) { + this._referenceToMemoryAddress.set(instructionReference, address); + } } - const specialEntriesToRemove = this._disassembledInstructions.length === 1 ? 1 : 0; - - // request is either at the start or end - if (instructionOffset >= 0) { - this._disassembledInstructions.splice(this._disassembledInstructions.length, specialEntriesToRemove, newEntries); - } else { - this._disassembledInstructions.splice(0, specialEntriesToRemove, newEntries); + if (newEntries.length === 0) { + return 0; } - return true; + const refBaseAddress = this._referenceToMemoryAddress.get(instructionReference); + const bps = this._instructionBpList.map(p => { + const base = this._referenceToMemoryAddress.get(p.instructionReference); + if (!base) { + return undefined; + } + return { + enabled: p.enabled, + address: base + BigInt(p.offset || 0), + }; + }); + + if (refBaseAddress !== undefined) { + for (const entry of newEntries) { + const bp = bps.find(p => p?.address === entry.address); + if (bp) { + entry.isBreakpointSet = true; + entry.isBreakpointEnabled = bp.enabled; + } + } + } + + const da = this._disassembledInstructions; + if (da.length === 1 && this._disassembledInstructions.row(0) === disassemblyNotAvailable) { + da.splice(0, 1); + } + + const firstAddr = newEntries[0].address; + const lastAddr = newEntries[newEntries.length - 1].address; + + const startN = binarySearch2(da.length, i => Number(da.row(i).address - firstAddr)); + const start = startN < 0 ? ~startN : startN; + const endN = binarySearch2(da.length, i => Number(da.row(i).address - lastAddr)); + const end = endN < 0 ? ~endN : endN; + const toDelete = end - start; + + // Go through everything we're about to add, and only show the source + // location if it's different from the previous one, "grouping" instructions by line + let lastLocated: undefined | DebugProtocol.DisassembledInstruction; + for (let i = start - 1; i >= 0; i--) { + const { instruction } = da.row(i); + if (instruction.location && instruction.line !== undefined) { + lastLocated = instruction; + break; + } + } + + const shouldShowLocation = (instruction: DebugProtocol.DisassembledInstruction) => + instruction.line !== undefined && instruction.location !== undefined && + (!lastLocated || !sourcesEqual(instruction.location, lastLocated.location) || instruction.line !== lastLocated.line); + + for (const entry of newEntries) { + if (shouldShowLocation(entry.instruction)) { + entry.showSourceLocation = true; + lastLocated = entry.instruction; + } + } + + da.splice(start, toDelete, newEntries); + + return newEntries.length - toDelete; } - return false; + return 0; } - private getIndexFromAddress(instructionAddress: string): number { + private getIndexFromReferenceAndOffset(instructionReference: string, offset: number): number { + const addr = this._referenceToMemoryAddress.get(instructionReference); + if (addr === undefined) { + return -1; + } + + return this.getIndexFromAddress(addr + BigInt(offset)); + } + + private getIndexFromAddress(address: bigint): number { const disassembledInstructions = this._disassembledInstructions; if (disassembledInstructions && disassembledInstructions.length > 0) { - const address = BigInt(instructionAddress); - if (address) { - return binarySearch2(disassembledInstructions.length, index => { - const row = disassembledInstructions.row(index); - - this.ensureAddressParsed(row); - if (row.instructionAddress! > address) { - return 1; - } else if (row.instructionAddress! < address) { - return -1; - } else { - return 0; - } - }); - } + return binarySearch2(disassembledInstructions.length, index => { + const row = disassembledInstructions.row(index); + return Number(row.address - address); + }); } return -1; } - private ensureAddressParsed(entry: IDisassembledInstructionEntry) { - if (entry.instructionAddress !== undefined) { - return; - } else { - entry.instructionAddress = BigInt(entry.instruction.address); - } - } - /** * Clears the table and reload instructions near the target address */ - private reloadDisassembly(targetAddress?: string) { - if (this._disassembledInstructions) { - this._loadingLock = true; // stop scrolling during the load. - this._disassembledInstructions.splice(0, this._disassembledInstructions.length, [disassemblyNotAvailable]); - this._instructionBpList = this._debugService.getModel().getInstructionBreakpoints(); - this.loadDisassembledInstructions(targetAddress, -DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD * 4, DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD * 8).then(() => { - // on load, set the target instruction in the middle of the page. - if (this._disassembledInstructions!.length > 0) { - const targetIndex = Math.floor(this._disassembledInstructions!.length / 2); - this._disassembledInstructions!.reveal(targetIndex, 0.5); - - // Always focus the target address on reload, or arrow key navigation would look terrible - this._disassembledInstructions!.domFocus(); - this._disassembledInstructions!.setFocus([targetIndex]); - } - this._loadingLock = false; - }); + private reloadDisassembly(instructionReference: string, offset: number) { + if (!this._disassembledInstructions) { + return; } + + this._loadingLock = true; // stop scrolling during the load. + this.clear(); + this._instructionBpList = this._debugService.getModel().getInstructionBreakpoints(); + this.loadDisassembledInstructions(instructionReference, offset, -DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD * 4, DisassemblyView.NUM_INSTRUCTIONS_TO_LOAD * 8).then(() => { + // on load, set the target instruction in the middle of the page. + if (this._disassembledInstructions!.length > 0) { + const targetIndex = Math.floor(this._disassembledInstructions!.length / 2); + this._disassembledInstructions!.reveal(targetIndex, 0.5); + + // Always focus the target address on reload, or arrow key navigation would look terrible + this._disassembledInstructions!.domFocus(); + this._disassembledInstructions!.setFocus([targetIndex]); + } + this._loadingLock = false; + }); + } + + private clear() { + this._referenceToMemoryAddress.clear(); + this._disassembledInstructions?.splice(0, this._disassembledInstructions.length, [disassemblyNotAvailable]); } } @@ -504,11 +673,12 @@ class BreakpointRenderer implements ITableRenderer | undefined; + private _languageSupportsDisassembleRequest: IContextKey | undefined; constructor( @IEditorService editorService: IEditorService, @@ -786,7 +956,7 @@ export class DisassemblyViewContribution implements IWorkbenchContribution { @IContextKeyService contextKeyService: IContextKeyService ) { contextKeyService.bufferChangeEvents(() => { - this._languageSupportsDisassemleRequest = CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST.bindTo(contextKeyService); + this._languageSupportsDisassembleRequest = CONTEXT_LANGUAGE_SUPPORTS_DISASSEMBLE_REQUEST.bindTo(contextKeyService); }); const onDidActiveEditorChangeListener = () => { @@ -800,13 +970,13 @@ export class DisassemblyViewContribution implements IWorkbenchContribution { const language = activeTextEditorControl.getModel()?.getLanguageId(); // TODO: instead of using idDebuggerInterestedInLanguage, have a specific ext point for languages // support disassembly - this._languageSupportsDisassemleRequest?.set(!!language && debugService.getAdapterManager().someDebuggerInterestedInLanguage(language)); + this._languageSupportsDisassembleRequest?.set(!!language && debugService.getAdapterManager().someDebuggerInterestedInLanguage(language)); this._onDidChangeModelLanguage = activeTextEditorControl.onDidChangeModelLanguage(e => { - this._languageSupportsDisassemleRequest?.set(debugService.getAdapterManager().someDebuggerInterestedInLanguage(e.newLanguage)); + this._languageSupportsDisassembleRequest?.set(debugService.getAdapterManager().someDebuggerInterestedInLanguage(e.newLanguage)); }); } else { - this._languageSupportsDisassemleRequest?.set(false); + this._languageSupportsDisassembleRequest?.set(false); } }; diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index eff654458ba..be84ffba9c6 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -4,14 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { ColorTransformType, asCssVariable, asCssVariableName, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugService, State, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { STATUS_BAR_FOREGROUND, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; +import { STATUS_BAR_FOREGROUND, STATUS_BAR_BORDER, COMMAND_CENTER_BACKGROUND } from 'vs/workbench/common/theme'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; + // colors for theming @@ -36,6 +38,18 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor hcLight: STATUS_BAR_BORDER }, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); +export const COMMAND_CENTER_DEBUGGING_BACKGROUND = registerColor( + 'commandCenter.debuggingBackground', + { + dark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + hcDark: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + light: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 }, + hcLight: { value: STATUS_BAR_DEBUGGING_BACKGROUND, op: ColorTransformType.Transparent, factor: 0.258 } + }, + localize('commandCenter-activeBackground', "Command center background color when a program is being debugged"), + true +); + export class StatusBarColorProvider implements IWorkbenchContribution { private readonly disposables = new DisposableStore(); @@ -63,12 +77,13 @@ export class StatusBarColorProvider implements IWorkbenchContribution { @IDebugService private readonly debugService: IDebugService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStatusbarService private readonly statusbarService: IStatusbarService, + @ILayoutService private readonly layoutService: ILayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { this.debugService.onDidChangeState(this.update, this, this.disposables); this.contextService.onDidChangeWorkbenchState(this.update, this, this.disposables); this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('debug.enableStatusBarColor')) { + if (e.affectsConfiguration('debug.enableStatusBarColor') || e.affectsConfiguration('debug.toolBarLocation')) { this.update(); } }); @@ -76,12 +91,20 @@ export class StatusBarColorProvider implements IWorkbenchContribution { } protected update(): void { - const decorateStatusBar: boolean = this.configurationService.getValue('debug').enableStatusBarColor; - if (!decorateStatusBar) { + const debugConfig = this.configurationService.getValue('debug'); + const isInDebugMode = isStatusbarInDebugMode(this.debugService.state, this.debugService.getModel().getSessions()); + if (!debugConfig.enableStatusBarColor) { this.enabled = false; } else { - this.enabled = isStatusbarInDebugMode(this.debugService.state, this.debugService.getModel().getSessions()); + this.enabled = isInDebugMode; } + + const isInCommandCenter = debugConfig.toolBarLocation === 'commandCenter'; + this.layoutService.container.style.setProperty(asCssVariableName(COMMAND_CENTER_BACKGROUND), isInCommandCenter && isInDebugMode + ? asCssVariable(COMMAND_CENTER_DEBUGGING_BACKGROUND) + : '' + ); + } dispose(): void { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8975e581fc7..3620976fcbc 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -581,9 +581,11 @@ export interface IDataBreakpoint extends IBaseBreakpoint { } export interface IInstructionBreakpoint extends IBaseBreakpoint { - // instructionReference is the instruction 'address' from the debugger. readonly instructionReference: string; readonly offset?: number; + /** Original instruction memory address; display purposes only */ + readonly address: bigint; + toJSON(): DebugProtocol.InstructionBreakpoint; } export interface IExceptionInfo { @@ -677,7 +679,7 @@ export interface IDebugConfiguration { openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak'; openExplorerOnEnd: boolean; inlineValues: boolean | 'auto' | 'on' | 'off'; // boolean for back-compat - toolBarLocation: 'floating' | 'docked' | 'hidden'; + toolBarLocation: 'floating' | 'docked' | 'commandCenter' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; extensionHostDebugAdapter: boolean; @@ -1097,14 +1099,14 @@ export interface IDebugService { /** * Adds a new instruction breakpoint. */ - addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise; + addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise; /** * Removes all instruction breakpoints. If address is passed only removes the instruction breakpoint with the passed address. * The address should be the address string supplied by the debugger from the "Disassemble" request. * Notifies debug adapter of breakpoint changes. */ - removeInstructionBreakpoints(address?: string): Promise; + removeInstructionBreakpoints(instructionReference?: string, offset?: number): Promise; setExceptionBreakpointCondition(breakpoint: IExceptionBreakpoint, condition: string | undefined): Promise; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 15030ce7822..588ffece2f3 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -473,8 +473,9 @@ export class StackFrame implements IStackFrame { const threadStopReason = this.thread.stoppedDetails?.reason; if (this.instructionPointerReference && (threadStopReason === 'instruction breakpoint' || - (threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction'))) { - return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true }); + (threadStopReason === 'step' && this.thread.lastSteppingGranularity === 'instruction') || + editorService.activeEditor instanceof DisassemblyViewInput)) { + return editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true }); } if (this.source.available) { @@ -1142,12 +1143,13 @@ export class InstructionBreakpoint extends BaseBreakpoint implements IInstructio hitCondition: string | undefined, condition: string | undefined, logMessage: string | undefined, + public readonly address: bigint, id = generateUuid() ) { super(enabled, hitCondition, condition, logMessage, id); } - override toJSON(): any { + override toJSON(): DebugProtocol.InstructionBreakpoint { const result = super.toJSON(); result.instructionReference = this.instructionReference; result.offset = this.offset; @@ -1677,17 +1679,22 @@ export class DebugModel extends Disposable implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): void { - const newInstructionBreakpoint = new InstructionBreakpoint(address, offset, false, true, hitCondition, condition, undefined); + addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): void { + const newInstructionBreakpoint = new InstructionBreakpoint(instructionReference, offset, false, true, hitCondition, condition, undefined, address); this.instructionBreakpoints.push(newInstructionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newInstructionBreakpoint], sessionOnly: true }); } - removeInstructionBreakpoints(address?: string): void { - let removed: InstructionBreakpoint[]; - if (address) { - removed = this.instructionBreakpoints.filter(fbp => fbp.instructionReference === address); - this.instructionBreakpoints = this.instructionBreakpoints.filter(fbp => fbp.instructionReference !== address); + removeInstructionBreakpoints(instructionReference?: string, offset?: number): void { + let removed: InstructionBreakpoint[] = []; + if (instructionReference) { + for (let i = 0; i < this.instructionBreakpoints.length; i++) { + const ibp = this.instructionBreakpoints[i]; + if (ibp.instructionReference === instructionReference && (offset === undefined || ibp.offset === offset)) { + removed.push(ibp); + this.instructionBreakpoints.splice(i--, 1); + } + } } else { removed = this.instructionBreakpoints; this.instructionBreakpoints = []; diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 3597d92b56b..acf1e467817 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -373,3 +373,6 @@ export async function saveAllBeforeDebugStart(configurationService: IConfigurati } await configurationService.reloadConfiguration(); } + +export const sourcesEqual = (a: DebugProtocol.Source | undefined, b: DebugProtocol.Source | undefined): boolean => + !a || !b ? a === b : a.name === b.name && a.path === b.path && a.sourceReference === b.sourceReference; diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index 9164b7458a8..8df100fe31d 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -8,7 +8,6 @@ import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; @@ -38,8 +37,6 @@ suite('Debug - Base Debug View', () => { disposables.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - test('render view tree', () => { const container = $('.container'); const treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index db19f575765..f3ef9c241e8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { OverviewRulerLane } from 'vs/editor/common/model'; @@ -59,8 +58,6 @@ suite('Debug - Breakpoints', () => { disposables.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - // Breakpoints test('simple', () => { @@ -296,7 +293,7 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; disposables.add(model.onDidChangeBreakpoints(() => eventCount++)); //address: string, offset: number, condition?: string, hitCondition?: string - model.addInstructionBreakpoint('0xCCCCFFFF', 0); + model.addInstructionBreakpoint('0xCCCCFFFF', 0, 0n); assert.strictEqual(eventCount, 1); let instructionBreakpoints = model.getInstructionBreakpoints(); @@ -304,7 +301,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(instructionBreakpoints[0].instructionReference, '0xCCCCFFFF'); assert.strictEqual(instructionBreakpoints[0].offset, 0); - model.addInstructionBreakpoint('0xCCCCEEEE', 1); + model.addInstructionBreakpoint('0xCCCCEEEE', 1, 0n); assert.strictEqual(eventCount, 2); instructionBreakpoints = model.getInstructionBreakpoints(); assert.strictEqual(instructionBreakpoints.length, 2); diff --git a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts index 9a9f2a09d63..4b463faf2c6 100644 --- a/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/callStack.test.ts @@ -9,7 +9,6 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { generateUuid } from 'vs/base/common/uuid'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -85,8 +84,6 @@ suite('Debug - CallStack', () => { sinon.restore(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - // Threads test('threads simple', () => { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index baade0af6d2..c6901f77f85 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -17,7 +17,7 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { createTestSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test'; import { createMockDebugModel } from 'vs/workbench/contrib/debug/test/browser/mockDebugModel'; -import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { ansiColorMap, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('Debug - ANSI Handling', () => { @@ -45,6 +45,7 @@ suite('Debug - ANSI Handling', () => { } const testTheme = new TestColorTheme(colors); themeService = new TestThemeService(testTheme); + registerColors(); }); teardown(() => { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts index f71a916f0c0..5b4781cb15a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugHover.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover'; import type { IExpression, IScope } from 'vs/workbench/contrib/debug/common/debug'; @@ -24,8 +23,6 @@ suite('Debug - Hover', () => { disposables.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - test('find expression in stack frame', async () => { const model = createMockDebugModel(disposables); const session = disposables.add(createTestSession(model)); diff --git a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts index b1dbc030a48..f443f3de650 100644 --- a/src/vs/workbench/contrib/debug/test/browser/repl.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/repl.test.ts @@ -9,7 +9,6 @@ import { TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import severity from 'vs/base/common/severity'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter'; @@ -36,8 +35,6 @@ suite('Debug - REPL', () => { disposables.dispose(); }); - ensureNoDisposablesAreLeakedInTestSuite(); - test('repl output', () => { const session = disposables.add(createTestSession(model)); const repl = new ReplModel(configurationService); diff --git a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts index a39f590b616..19027ec17d6 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugModel.test.ts @@ -7,15 +7,12 @@ import * as assert from 'assert'; import { DeferredPromise } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mockObject } from 'vs/base/test/common/mock'; -import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { NullLogService } from 'vs/platform/log/common/log'; import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { MockDebugStorage } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; suite('DebugModel', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - suite('FunctionBreakpoint', () => { test('Id is saved', () => { const fbp = new FunctionBreakpoint('function', true, 'hit condition', 'condition', 'log message'); @@ -41,6 +38,8 @@ suite('DebugModel', () => { const wholeStackDeferred = new DeferredPromise(); const fakeThread = mockObject()({ session: { capabilities: { supportsDelayedStackTraceLoading: true } } as any, + getCallStack: () => [], + getStaleCallStack: () => [], }); fakeThread.fetchCallStack.callsFake((levels: number) => { return levels === 1 ? topFrameDeferred.p : wholeStackDeferred.p; diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 30353a78a35..edd1cdfc1cd 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -81,7 +81,7 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - addInstructionBreakpoint(address: string, offset: number, condition?: string, hitCondition?: string): Promise { + addInstructionBreakpoint(instructionReference: string, offset: number, address: bigint, condition?: string, hitCondition?: string): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 9495543aad6..886278071b0 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -448,6 +448,9 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo async run(accessor: ServicesAccessor, editSessionId?: string): Promise { const data = await that.quickInputService.input({ prompt: 'Enter serialized data' }); + if (data) { + that.editSessionsStorageService.lastReadResources.set('editSessions', { content: data, ref: '' }); + } await that.progressService.withProgress({ ...resumeProgressOptions, title: resumeProgressOptionsTitle }, async () => await that.resumeEditSession(editSessionId, undefined, undefined, undefined, undefined, data)); } })); diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts index 937cbdac372..7c0e75f1e06 100644 --- a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts +++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; @@ -18,7 +19,7 @@ export class EditSessionsLogService extends AbstractLogger implements IEditSessi @IEnvironmentService environmentService: IEnvironmentService ) { super(); - this.logger = this._register(loggerService.createLogger(editSessionsLogId, { name: localize('cloudChangesLog', "Cloud Changes") })); + this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${editSessionsLogId}.log`), { id: editSessionsLogId, name: localize('cloudChangesLog', "Cloud Changes") })); } trace(message: string, ...args: any[]): void { diff --git a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts index aea881b28eb..83372f3c0c7 100644 --- a/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts +++ b/src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts @@ -16,20 +16,20 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview, ISyncResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer'; -import { IRemoteUserData, IResourceRefHandle, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IRemoteUserData, IResourceRefHandle, IUserDataSyncLocalStoreService, IUserDataSyncConfiguration, IUserDataSyncEnablementService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSynchroniser, IWorkspaceState, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { EditSession, IEditSessionsStorageService } from 'vs/workbench/contrib/editSessions/common/editSessions'; import { IWorkspaceIdentityService } from 'vs/workbench/services/workspaces/common/workspaceIdentityService'; -class NullBackupStoreService implements IUserDataSyncBackupStoreService { +class NullBackupStoreService implements IUserDataSyncLocalStoreService { _serviceBrand: undefined; - async backup(profile: IUserDataProfile, resource: SyncResource, content: string): Promise { + async writeResource(): Promise { return; } - async getAllRefs(profile: IUserDataProfile, resource: SyncResource): Promise { + async getAllResourceRefs(): Promise { return []; } - async resolveContent(profile: IUserDataProfile, resource: SyncResource, ref: string): Promise { + async resolveResourceContent(): Promise { return null; } @@ -70,9 +70,9 @@ export class WorkspaceStateSynchroniser extends AbstractSynchroniser implements @IWorkspaceIdentityService private readonly workspaceIdentityService: IWorkspaceIdentityService, @IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService, ) { - const userDataSyncBackupStoreService = new NullBackupStoreService(); + const userDataSyncLocalStoreService = new NullBackupStoreService(); const userDataSyncEnablementService = new NullEnablementService(); - super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + super({ syncResource: SyncResource.WorkspaceState, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncLocalStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); } override async sync(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts index c816b6686ea..e840bd9b331 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts @@ -9,7 +9,7 @@ import { CancelablePromise, createCancelablePromise, Promises, raceCancellablePr import { CancellationToken } from 'vs/base/common/cancellation'; import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, isDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, isDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -50,12 +50,12 @@ type RecommendationsNotificationActions = { onDidNeverShowRecommendedExtensionsAgain(extensions: IExtension[]): void; }; -class RecommendationsNotification { +class RecommendationsNotification extends Disposable { - private _onDidClose = new Emitter(); + private _onDidClose = this._register(new Emitter()); readonly onDidClose = this._onDidClose.event; - private _onDidChangeVisibility = new Emitter(); + private _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility = this._onDidChangeVisibility.event; private notificationHandle: INotificationHandle | undefined; @@ -66,7 +66,9 @@ class RecommendationsNotification { private readonly message: string, private readonly choices: IPromptChoice[], private readonly notificationService: INotificationService - ) { } + ) { + super(); + } show(): void { if (!this.notificationHandle) { @@ -87,8 +89,8 @@ class RecommendationsNotification { return this.cancelled; } - private onDidCloseDisposable = new MutableDisposable(); - private onDidChangeVisibilityDisposable = new MutableDisposable(); + private onDidCloseDisposable = this._register(new MutableDisposable()); + private onDidChangeVisibilityDisposable = this._register(new MutableDisposable()); private updateNotificationHandle(notificationHandle: INotificationHandle) { this.onDidCloseDisposable.clear(); this.onDidChangeVisibilityDisposable.clear(); @@ -110,7 +112,7 @@ class RecommendationsNotification { type PendingRecommendationsNotification = { recommendationsNotification: RecommendationsNotification; source: RecommendationSource; token: CancellationToken }; type VisibleRecommendationsNotification = { recommendationsNotification: RecommendationsNotification; source: RecommendationSource; from: number }; -export class ExtensionRecommendationNotificationService implements IExtensionRecommendationNotificationService { +export class ExtensionRecommendationNotificationService extends Disposable implements IExtensionRecommendationNotificationService { declare readonly _serviceBrand: undefined; @@ -138,7 +140,9 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - ) { } + ) { + super(); + } hasToIgnoreRecommendationNotifications(): boolean { const config = this.configurationService.getValue<{ ignoreRecommendations: boolean; showRecommendationsOnlyOnDemand?: boolean }>('extensions'); @@ -255,8 +259,8 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec } return raceCancellablePromises([ - this.showRecommendationsNotification(extensions, message, searchValue, source, recommendationsNotificationActions), - this.waitUntilRecommendationsAreInstalled(extensions) + this._registerP(this.showRecommendationsNotification(extensions, message, searchValue, source, recommendationsNotificationActions)), + this._registerP(this.waitUntilRecommendationsAreInstalled(extensions)) ]); } @@ -344,11 +348,11 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec private async doShowRecommendationsNotification(severity: Severity, message: string, choices: IPromptChoice[], source: RecommendationSource, token: CancellationToken): Promise { const disposables = new DisposableStore(); try { - const recommendationsNotification = new RecommendationsNotification(severity, message, choices, this.notificationService); - Event.once(Event.filter(recommendationsNotification.onDidChangeVisibility, e => !e))(() => this.showNextNotification()); + const recommendationsNotification = disposables.add(new RecommendationsNotification(severity, message, choices, this.notificationService)); + disposables.add(Event.once(Event.filter(recommendationsNotification.onDidChangeVisibility, e => !e))(() => this.showNextNotification())); if (this.visibleNotification) { const index = this.pendingNotificaitons.length; - token.onCancellationRequested(() => this.pendingNotificaitons.splice(index, 1), disposables); + disposables.add(token.onCancellationRequested(() => this.pendingNotificaitons.splice(index, 1))); this.pendingNotificaitons.push({ recommendationsNotification, source, token }); if (source !== RecommendationSource.EXE && source <= this.visibleNotification!.source) { this.hideVisibleNotification(3000); @@ -357,7 +361,7 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec this.visibleNotification = { recommendationsNotification, source, from: Date.now() }; recommendationsNotification.show(); } - await raceCancellation(Event.toPromise(recommendationsNotification.onDidClose), token); + await raceCancellation(new Promise(c => disposables.add(Event.once(recommendationsNotification.onDidClose)(c))), token); return !recommendationsNotification.isCancelled(); } finally { disposables.dispose(); @@ -442,4 +446,9 @@ export class ExtensionRecommendationNotificationService implements IExtensionRec private setIgnoreRecommendationsConfig(configVal: boolean) { this.configurationService.updateValue('extensions.ignoreRecommendations', configVal); } + + private _registerP(o: CancelablePromise): CancelablePromise { + this._register(toDisposable(() => o.cancel())); + return o; + } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 3dd31a217ff..a6806b20159 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, IExtensionGalleryService, InstallOperation, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationsService, ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -20,7 +20,7 @@ import { LanguageRecommendations } from 'vs/workbench/contrib/extensions/browser import { ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { ConfigBasedRecommendations } from 'vs/workbench/contrib/extensions/browser/configBasedRecommendations'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; -import { timeout } from 'vs/base/common/async'; +import { CancelablePromise, timeout } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { WebRecommendations } from 'vs/workbench/contrib/extensions/browser/webRecommendations'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -71,14 +71,14 @@ export class ExtensionRecommendationsService extends Disposable implements IExte ) { super(); - this.workspaceRecommendations = instantiationService.createInstance(WorkspaceRecommendations); - this.fileBasedRecommendations = instantiationService.createInstance(FileBasedRecommendations); - this.configBasedRecommendations = instantiationService.createInstance(ConfigBasedRecommendations); - this.exeBasedRecommendations = instantiationService.createInstance(ExeBasedRecommendations); - this.keymapRecommendations = instantiationService.createInstance(KeymapRecommendations); - this.webRecommendations = instantiationService.createInstance(WebRecommendations); - this.languageRecommendations = instantiationService.createInstance(LanguageRecommendations); - this.remoteRecommendations = instantiationService.createInstance(RemoteRecommendations); + this.workspaceRecommendations = this._register(instantiationService.createInstance(WorkspaceRecommendations)); + this.fileBasedRecommendations = this._register(instantiationService.createInstance(FileBasedRecommendations)); + this.configBasedRecommendations = this._register(instantiationService.createInstance(ConfigBasedRecommendations)); + this.exeBasedRecommendations = this._register(instantiationService.createInstance(ExeBasedRecommendations)); + this.keymapRecommendations = this._register(instantiationService.createInstance(KeymapRecommendations)); + this.webRecommendations = this._register(instantiationService.createInstance(WebRecommendations)); + this.languageRecommendations = this._register(instantiationService.createInstance(LanguageRecommendations)); + this.remoteRecommendations = this._register(instantiationService.createInstance(RemoteRecommendations)); if (!this.isEnabled()) { this.sessionSeed = 0; @@ -276,8 +276,13 @@ export class ExtensionRecommendationsService extends Disposable implements IExte .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)); if (allowedRecommendations.length) { - await timeout(5000); + await this._registerP(timeout(5000)); await this.extensionRecommendationNotificationService.promptWorkspaceRecommendations(allowedRecommendations); } } + + private _registerP(o: CancelablePromise): CancelablePromise { + this._register(toDisposable(() => o.cancel())); + return o; + } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts index 6370029fda8..5e7c228e17e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts @@ -10,6 +10,7 @@ import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class ExtensionActivationProgress implements IWorkbenchContribution { @@ -39,7 +40,7 @@ export class ExtensionActivationProgress implements IWorkbenchContribution { count++; - Promise.race([e.activation, timeout(5000)]).finally(() => { + Promise.race([e.activation, timeout(5000, CancellationToken.None)]).finally(() => { if (--count === 0) { deferred!.complete(undefined); deferred = undefined; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts index d19b0fedbdd..c644f0acb11 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionRecommendationsService.test.ts @@ -61,6 +61,9 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -180,6 +183,7 @@ function aGalleryExtension(name: string, properties: any = {}, galleryExtensionP } suite('ExtensionRecommendationsService Test', () => { + let disposableStore: DisposableStore; let workspaceService: IWorkspaceContextService; let instantiationService: TestInstantiationService; let testConfigurationService: TestConfigurationService; @@ -189,18 +193,27 @@ suite('ExtensionRecommendationsService Test', () => { uninstallEvent: Emitter, didUninstallEvent: Emitter; let prompted: boolean; - const promptedEmitter = new Emitter(); + let promptedEmitter: Emitter; let onModelAddedEvent: Emitter; - suiteSetup(() => { - instantiationService = new TestInstantiationService(); - installEvent = new Emitter(); - didInstallEvent = new Emitter(); - uninstallEvent = new Emitter(); - didUninstallEvent = new Emitter(); + teardown(async () => { + disposableStore.dispose(); + await timeout(0); // allow for async disposables to complete + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + disposableStore = new DisposableStore(); + instantiationService = disposableStore.add(new TestInstantiationService()); + promptedEmitter = disposableStore.add(new Emitter()); + installEvent = disposableStore.add(new Emitter()); + didInstallEvent = disposableStore.add(new Emitter()); + uninstallEvent = disposableStore.add(new Emitter()); + didUninstallEvent = disposableStore.add(new Emitter()); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(ISharedProcessService, TestSharedProcessService); - instantiationService.stub(ILifecycleService, new TestLifecycleService()); + instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService())); testConfigurationService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(INotificationService, new TestNotificationService()); @@ -222,11 +235,11 @@ suite('ExtensionRecommendationsService Test', () => { extensions: [], async whenInstalledExtensionsRegistered() { return true; } }); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IURLService, NativeURLService); instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService()); - instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(IStorageService, disposableStore.add(new TestStorageService())); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IProductService, >{ extensionTips: { @@ -275,13 +288,11 @@ suite('ExtensionRecommendationsService Test', () => { }, }); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService)); + instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService))); onModelAddedEvent = new Emitter(); - }); - setup(() => { instantiationService.stub(IEnvironmentService, >{}); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); @@ -307,12 +318,6 @@ suite('ExtensionRecommendationsService Test', () => { }); }); - teardown(() => (testObject).dispose()); - - suiteTeardown(() => { - instantiationService.dispose(); - }); - function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise { return setUpFolder(folderName, recommendedExtensions, ignoredRecommendations); } @@ -320,9 +325,9 @@ suite('ExtensionRecommendationsService Test', () => { async function setUpFolder(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise { const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); const logService = new NullLogService(); - const fileService = new FileService(logService); - const fileSystemProvider = new InMemoryFileSystemProvider(); - fileService.registerProvider(ROOT.scheme, fileSystemProvider); + const fileService = disposableStore.add(new FileService(logService)); + const fileSystemProvider = disposableStore.add(new InMemoryFileSystemProvider()); + disposableStore.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); const folderDir = joinPath(ROOT, folderName); const workspaceSettingsDir = joinPath(folderDir, '.vscode'); @@ -338,14 +343,14 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.stub(IFileService, fileService); workspaceService = new TestContextService(myWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceService); - instantiationService.stub(IWorkspaceExtensionsConfigService, instantiationService.createInstance(WorkspaceExtensionsConfigService)); - instantiationService.stub(IExtensionIgnoredRecommendationsService, instantiationService.createInstance(ExtensionIgnoredRecommendationsService)); - instantiationService.stub(IExtensionRecommendationNotificationService, instantiationService.createInstance(ExtensionRecommendationNotificationService)); + instantiationService.stub(IWorkspaceExtensionsConfigService, disposableStore.add(instantiationService.createInstance(WorkspaceExtensionsConfigService))); + instantiationService.stub(IExtensionIgnoredRecommendationsService, disposableStore.add(instantiationService.createInstance(ExtensionIgnoredRecommendationsService))); + instantiationService.stub(IExtensionRecommendationNotificationService, disposableStore.add(instantiationService.createInstance(ExtensionRecommendationNotificationService))); } function testNoPromptForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', recommendations).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); return testObject.activationPromise.then(() => { assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); assert.ok(!prompted); @@ -355,7 +360,7 @@ suite('ExtensionRecommendationsService Test', () => { function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) { return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); assert.ok(!prompted); return testObject.getWorkspaceRecommendations().then(() => { @@ -384,7 +389,7 @@ suite('ExtensionRecommendationsService Test', () => { test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', () => runWithFakedTimers({ useFakeTimers: true }, async () => { await setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions); - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); await Event.toPromise(promptedEmitter.event); const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); @@ -413,7 +418,7 @@ suite('ExtensionRecommendationsService Test', () => { test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => runWithFakedTimers({ useFakeTimers: true }, async () => { testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true }); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); return testObject.activationPromise.then(() => { assert.ok(!prompted); }); @@ -431,7 +436,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.PROFILE, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); return testObject.activationPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been globally ignored @@ -449,7 +454,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); return testObject.activationPromise.then(() => { const recommendations = testObject.getAllRecommendationsWithReason(); assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been workspace ignored @@ -471,7 +476,7 @@ suite('ExtensionRecommendationsService Test', () => { storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations); - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); await testObject.activationPromise; const recommendations = testObject.getAllRecommendationsWithReason(); @@ -489,7 +494,7 @@ suite('ExtensionRecommendationsService Test', () => { await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions); const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService); - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); await testObject.activationPromise; let recommendations = testObject.getAllRecommendationsWithReason(); @@ -521,9 +526,9 @@ suite('ExtensionRecommendationsService Test', () => { storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.PROFILE, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', []); - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService); - extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget); + disposableStore.add(extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget)); extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true); await testObject.activationPromise; @@ -536,7 +541,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE); return setUpFolderWorkspace('myFolder', []).then(() => { - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); return testObject.activationPromise.then(() => { const recommendations = testObject.getFileBasedRecommendations(); assert.strictEqual(recommendations.length, 2); @@ -555,7 +560,7 @@ suite('ExtensionRecommendationsService Test', () => { instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE); await setUpFolderWorkspace('myFolder', []); - testObject = instantiationService.createInstance(ExtensionRecommendationsService); + testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService)); await testObject.activationPromise; const recommendations = testObject.getFileBasedRecommendations(); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts index 36a58fb81ba..c811b48b7bf 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsActions.test.ts @@ -55,6 +55,7 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; let instantiationService: TestInstantiationService; let installEvent: Emitter, @@ -62,14 +63,11 @@ let installEvent: Emitter, uninstallEvent: Emitter, didUninstallEvent: Emitter; -let disposables: DisposableStore; - -function setupTest() { - disposables = new DisposableStore(); - installEvent = new Emitter(); - didInstallEvent = new Emitter(); - uninstallEvent = new Emitter(); - didUninstallEvent = new Emitter(); +function setupTest(disposables: Pick) { + installEvent = disposables.add(new Emitter()); + didInstallEvent = disposables.add(new Emitter()); + uninstallEvent = disposables.add(new Emitter()); + didUninstallEvent = disposables.add(new Emitter()); instantiationService = disposables.add(new TestInstantiationService()); @@ -122,11 +120,11 @@ function setupTest() { } }); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - instantiationService.stub(ILabelService, { onDidChangeFormatters: new Emitter().event }); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + instantiationService.stub(ILabelService, { onDidChangeFormatters: disposables.add(new Emitter()).event }); - instantiationService.stub(ILifecycleService, new TestLifecycleService()); - instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService)); + instantiationService.stub(ILifecycleService, disposables.add(new TestLifecycleService())); + instantiationService.stub(IExtensionTipsService, disposables.add(instantiationService.createInstance(TestExtensionTipsService))); instantiationService.stub(IExtensionRecommendationsService, {}); instantiationService.stub(IURLService, NativeURLService); @@ -136,7 +134,7 @@ function setupTest() { instantiationService.stub(IExtensionService, >{ extensions: [], onDidChangeExtensions: Event.None, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); - instantiationService.stub(IUserDataSyncEnablementService, instantiationService.createInstance(UserDataSyncEnablementService)); + instantiationService.stub(IUserDataSyncEnablementService, disposables.add(instantiationService.createInstance(UserDataSyncEnablementService))); instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); @@ -145,19 +143,19 @@ function setupTest() { suite('ExtensionsActions', () => { - setup(setupTest); - teardown(() => disposables.dispose()); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + setup(() => setupTest(disposables)); test('Install action is disabled when there is no extension', () => { - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false }); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false })); assert.ok(!testObject.enabled); }); test('Test Install action when state is installed', () => { const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false }); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false })); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); return workbenchService.queryLocal() @@ -175,8 +173,8 @@ suite('ExtensionsActions', () => { test('Test InstallingLabelAction when state is installing', () => { const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallingLabelAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallingLabelAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); return workbenchService.queryGallery(CancellationToken.None) @@ -192,8 +190,8 @@ suite('ExtensionsActions', () => { test('Test Install action when state is uninstalled', async () => { const workbenchService = instantiationService.get(IExtensionsWorkbenchService); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false }); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false })); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const paged = await workbenchService.queryGallery(CancellationToken.None); @@ -205,8 +203,8 @@ suite('ExtensionsActions', () => { }); test('Test Install action when extension is system action', () => { - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false }); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false })); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', {}, { type: ExtensionType.System }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -220,8 +218,8 @@ suite('ExtensionsActions', () => { }); test('Test Install action when extension doesnot has gallery', () => { - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false }); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.InstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.InstallAction, { installPreReleaseVersion: false })); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -235,15 +233,15 @@ suite('ExtensionsActions', () => { }); test('Uninstall action is disabled when there is no extension', () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); assert.ok(!testObject.enabled); }); test('Test Uninstall action when state is uninstalling', () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -258,8 +256,8 @@ suite('ExtensionsActions', () => { }); test('Test Uninstall action when state is installed and is user extension', () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -273,8 +271,8 @@ suite('ExtensionsActions', () => { }); test('Test Uninstall action when state is installed and is system extension', () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', {}, { type: ExtensionType.System }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -288,8 +286,8 @@ suite('ExtensionsActions', () => { }); test('Test Uninstall action when state is installing and is user extension', () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -305,8 +303,8 @@ suite('ExtensionsActions', () => { }); test('Test Uninstall action after extension is installed', async () => { - const testObject: ExtensionsActions.UninstallAction = instantiationService.createInstance(ExtensionsActions.UninstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UninstallAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UninstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -324,15 +322,15 @@ suite('ExtensionsActions', () => { }); test('Test UpdateAction when there is no extension', () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); assert.ok(!testObject.enabled); }); test('Test UpdateAction when extension is uninstalled', () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a', { version: '1.0.0' }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) @@ -343,8 +341,8 @@ suite('ExtensionsActions', () => { }); test('Test UpdateAction when extension is installed and not outdated', () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.0' }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -358,8 +356,8 @@ suite('ExtensionsActions', () => { }); test('Test UpdateAction when extension is installed outdated and system extension', () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.0' }, { type: ExtensionType.System }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -373,8 +371,8 @@ suite('ExtensionsActions', () => { }); test('Test UpdateAction when extension is installed outdated and user extension', () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.0' }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -388,19 +386,19 @@ suite('ExtensionsActions', () => { instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [gallery]); assert.ok(!testObject.enabled); return new Promise(c => { - testObject.onDidChange(() => { + disposables.add(testObject.onDidChange(() => { if (testObject.enabled) { c(); } - }); + })); instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); }); }); }); test('Test UpdateAction when extension is installing and outdated and user extension', async () => { - const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.UpdateAction = disposables.add(instantiationService.createInstance(ExtensionsActions.UpdateAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.0' }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -411,33 +409,33 @@ suite('ExtensionsActions', () => { instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery); instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', [gallery]); await new Promise(c => { - testObject.onDidChange(() => { + disposables.add(testObject.onDidChange(() => { if (testObject.enabled) { c(); } - }); + })); instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); }); await new Promise(c => { - testObject.onDidChange(() => { + disposables.add(testObject.onDidChange(() => { if (!testObject.enabled) { c(); } - }); + })); installEvent.fire({ identifier: local.identifier, source: gallery }); }); }); test('Test ManageExtensionAction when there is no extension', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); assert.ok(!testObject.enabled); }); test('Test ManageExtensionAction when extension is installed', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -451,8 +449,8 @@ suite('ExtensionsActions', () => { }); test('Test ManageExtensionAction when extension is uninstalled', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -466,8 +464,8 @@ suite('ExtensionsActions', () => { }); test('Test ManageExtensionAction when extension is installing', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -483,8 +481,8 @@ suite('ExtensionsActions', () => { }); test('Test ManageExtensionAction when extension is queried from gallery and installed', async () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -501,8 +499,8 @@ suite('ExtensionsActions', () => { }); test('Test ManageExtensionAction when extension is system extension', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', {}, { type: ExtensionType.System }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -516,8 +514,8 @@ suite('ExtensionsActions', () => { }); test('Test ManageExtensionAction when extension is uninstalling', () => { - const testObject: ExtensionsActions.ManageExtensionAction = instantiationService.createInstance(ExtensionsActions.ManageExtensionAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ManageExtensionAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ManageExtensionAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -533,7 +531,7 @@ suite('ExtensionsActions', () => { }); test('Test EnableForWorkspaceAction when there is no extension', () => { - const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction); + const testObject: ExtensionsActions.EnableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction)); assert.ok(!testObject.enabled); }); @@ -544,7 +542,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction); + const testObject: ExtensionsActions.EnableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -558,7 +556,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction); + const testObject: ExtensionsActions.EnableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -573,7 +571,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction); + const testObject: ExtensionsActions.EnableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -589,7 +587,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction); + const testObject: ExtensionsActions.EnableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -597,7 +595,7 @@ suite('ExtensionsActions', () => { }); test('Test EnableGloballyAction when there is no extension', () => { - const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction); + const testObject: ExtensionsActions.EnableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableGloballyAction)); assert.ok(!testObject.enabled); }); @@ -608,7 +606,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction); + const testObject: ExtensionsActions.EnableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableGloballyAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -622,7 +620,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction); + const testObject: ExtensionsActions.EnableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableGloballyAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -637,7 +635,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction); + const testObject: ExtensionsActions.EnableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableGloballyAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -653,7 +651,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableGloballyAction = instantiationService.createInstance(ExtensionsActions.EnableGloballyAction); + const testObject: ExtensionsActions.EnableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableGloballyAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -661,7 +659,7 @@ suite('ExtensionsActions', () => { }); test('Test EnableAction when there is no extension', () => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); assert.ok(!testObject.enabled); }); @@ -672,7 +670,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -686,7 +684,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -701,7 +699,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -714,7 +712,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); }); @@ -726,9 +724,9 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = page.firstPage[0]; - instantiationService.createInstance(ExtensionContainers, [testObject]); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); installEvent.fire({ identifier: gallery.identifier, source: gallery }); assert.ok(!testObject.enabled); @@ -741,7 +739,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.EnableDropDownAction = instantiationService.createInstance(ExtensionsActions.EnableDropDownAction); + const testObject: ExtensionsActions.EnableDropDownAction = disposables.add(instantiationService.createInstance(ExtensionsActions.EnableDropDownAction)); testObject.extension = extensions[0]; uninstallEvent.fire({ identifier: local.identifier }); assert.ok(!testObject.enabled); @@ -749,7 +747,7 @@ suite('ExtensionsActions', () => { }); test('Test DisableForWorkspaceAction when there is no extension', () => { - const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction); + const testObject: ExtensionsActions.DisableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction)); assert.ok(!testObject.enabled); }); @@ -762,7 +760,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction); + const testObject: ExtensionsActions.DisableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -777,7 +775,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction); + const testObject: ExtensionsActions.DisableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -796,14 +794,14 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableForWorkspaceAction = instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction); + const testObject: ExtensionsActions.DisableForWorkspaceAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableForWorkspaceAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); }); test('Test DisableGloballyAction when there is no extension', () => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); assert.ok(!testObject.enabled); }); @@ -816,7 +814,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -831,7 +829,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -849,7 +847,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -866,7 +864,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; assert.ok(testObject.enabled); }); @@ -886,7 +884,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; assert.ok(!testObject.enabled); }); @@ -904,7 +902,7 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = page.firstPage[0]; assert.ok(!testObject.enabled); }); @@ -921,9 +919,9 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None) .then(page => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = page.firstPage[0]; - instantiationService.createInstance(ExtensionContainers, [testObject]); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); installEvent.fire({ identifier: gallery.identifier, source: gallery }); assert.ok(!testObject.enabled); }); @@ -940,9 +938,9 @@ suite('ExtensionsActions', () => { return instantiationService.get(IExtensionsWorkbenchService).queryLocal() .then(extensions => { - const testObject: ExtensionsActions.DisableGloballyAction = instantiationService.createInstance(ExtensionsActions.DisableGloballyAction); + const testObject: ExtensionsActions.DisableGloballyAction = disposables.add(instantiationService.createInstance(ExtensionsActions.DisableGloballyAction)); testObject.extension = extensions[0]; - instantiationService.createInstance(ExtensionContainers, [testObject]); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); uninstallEvent.fire({ identifier: local.identifier }); assert.ok(!testObject.enabled); }); @@ -952,19 +950,20 @@ suite('ExtensionsActions', () => { suite('ReloadAction', () => { - setup(setupTest); - teardown(() => disposables.dispose()); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => setupTest(disposables)); test('Test ReloadAction when there is no extension', () => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); assert.ok(!testObject.enabled); }); test('Test ReloadAction when extension state is installing', async () => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -976,8 +975,8 @@ suite('ReloadAction', () => { }); test('Test ReloadAction when extension state is uninstalling', async () => { - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -995,8 +994,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1020,8 +1019,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => true, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1042,8 +1041,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); const paged = await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None); @@ -1066,9 +1065,9 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); @@ -1089,8 +1088,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => true, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); testObject.extension = extensions[0]; @@ -1108,8 +1107,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await instantiationService.get(IExtensionsWorkbenchService).queryLocal(); @@ -1134,9 +1133,9 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.1' }); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1144,11 +1143,11 @@ suite('ReloadAction', () => { testObject.extension = extensions[0]; return new Promise(c => { - testObject.onDidChange(() => { + disposables.add(testObject.onDidChange(() => { if (testObject.enabled && testObject.tooltip === 'Please reload Visual Studio Code to enable the updated extension.') { c(); } - }); + })); const gallery = aGalleryExtension('a', { uuid: local.identifier.id, version: '1.0.2' }); installEvent.fire({ identifier: gallery.identifier, source: gallery }); didInstallEvent.fire([{ identifier: gallery.identifier, source: gallery, operation: InstallOperation.Install, local: aLocalExtension('a', gallery, gallery) }]); @@ -1165,8 +1164,8 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a', { version: '1.0.1' }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await workbenchService.queryLocal(); @@ -1187,9 +1186,9 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1210,9 +1209,9 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.set(IExtensionsWorkbenchService, disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a'); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1233,8 +1232,8 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await workbenchService.queryLocal(); @@ -1255,8 +1254,8 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a'); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await workbenchService.queryLocal(); @@ -1276,8 +1275,8 @@ suite('ReloadAction', () => { }); const local = aLocalExtension('a', { version: '1.0.1' }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([local], EnablementState.DisabledGlobally); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); const extensions = await workbenchService.queryLocal(); @@ -1300,8 +1299,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const gallery = aGalleryExtension('a'); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); @@ -1322,8 +1321,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const local = aLocalExtension('a', { version: '1.0.1', contributes: { localizations: [{ languageId: 'de', translations: [] }] } }); const workbenchService = instantiationService.get(IExtensionsWorkbenchService); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); @@ -1343,7 +1342,7 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); instantiationService.stub(IExtensionService, >{ extensions: [toExtensionDescription(remoteExtension)], @@ -1351,11 +1350,11 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1377,7 +1376,7 @@ suite('ReloadAction', () => { localExtensionManagementService.onDidUninstallExtension = onDidUninstallEvent.event; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); instantiationService.stub(IExtensionService, >{ extensions: [toExtensionDescription(remoteExtension)], @@ -1385,11 +1384,11 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1413,8 +1412,8 @@ suite('ReloadAction', () => { const localExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file('pub.a') }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1424,8 +1423,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1452,8 +1451,8 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1463,8 +1462,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1492,7 +1491,7 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); instantiationService.stub(IExtensionService, >{ extensions: [toExtensionDescription(localExtension)], @@ -1500,11 +1499,11 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1524,8 +1523,8 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1535,8 +1534,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1556,8 +1555,8 @@ suite('ReloadAction', () => { remoteExtensionManagementService.onDidInstallExtensions = onDidInstallEvent.event; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1567,8 +1566,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1588,8 +1587,8 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1599,8 +1598,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1620,8 +1619,8 @@ suite('ReloadAction', () => { remoteExtensionManagementService.onDidInstallExtensions = onDidInstallEvent.event; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const onDidChangeExtensionsEmitter = new Emitter<{ added: IExtensionDescription[]; removed: IExtensionDescription[] }>(); @@ -1631,8 +1630,8 @@ suite('ReloadAction', () => { canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1649,18 +1648,18 @@ suite('ReloadAction', () => { const remoteExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'], 'browser': 'browser.js' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, null, createExtensionManagementService([remoteExtension]), createExtensionManagementService([webExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); instantiationService.stub(IExtensionService, >{ extensions: [toExtensionDescription(remoteExtension)], onDidChangeExtensions: Event.None, canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1677,18 +1676,18 @@ suite('ReloadAction', () => { const localExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'], 'browser': 'browser.js' }, { location: URI.file('pub.a') }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), null, createExtensionManagementService([webExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); instantiationService.stub(IExtensionService, >{ extensions: [toExtensionDescription(localExtension)], onDidChangeExtensions: Event.None, canAddExtension: (extension) => false, whenInstalledExtensionsRegistered: () => Promise.resolve(true) }); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); - const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject: ExtensionsActions.ReloadAction = disposables.add(instantiationService.createInstance(ExtensionsActions.ReloadAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); await workbenchService.queryGallery(CancellationToken.None); @@ -1701,21 +1700,22 @@ suite('ReloadAction', () => { suite('RemoteInstallAction', () => { - setup(setupTest); - teardown(() => disposables.dispose()); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => setupTest(disposables)); test('Test remote install action is enabled for local workspace extension', async () => { // multi server setup const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1733,15 +1733,15 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const gallery = aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1766,15 +1766,15 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), remoteExtensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const gallery = aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1800,15 +1800,15 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([remoteWorkspaceExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1823,14 +1823,14 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1845,14 +1845,14 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, true); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, true)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1867,14 +1867,14 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localWorkspaceExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1886,13 +1886,13 @@ suite('RemoteInstallAction', () => { // multi server setup const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1903,13 +1903,13 @@ suite('RemoteInstallAction', () => { // multi server setup const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const pager = await workbenchService.queryGallery(CancellationToken.None); testObject.extension = pager.firstPage[0]; @@ -1927,13 +1927,13 @@ suite('RemoteInstallAction', () => { instantiationService.stub(IWorkbenchEnvironmentService, environmentService); instantiationService.stub(INativeWorkbenchEnvironmentService, environmentService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1949,8 +1949,8 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1964,15 +1964,15 @@ suite('RemoteInstallAction', () => { const extensionManagementService = instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localWorkspaceExtension]); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -1990,13 +1990,13 @@ suite('RemoteInstallAction', () => { const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2010,13 +2010,13 @@ suite('RemoteInstallAction', () => { const localWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2029,13 +2029,13 @@ suite('RemoteInstallAction', () => { const localWorkspaceSystemExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceSystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceSystemExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2048,13 +2048,13 @@ suite('RemoteInstallAction', () => { const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2068,13 +2068,13 @@ suite('RemoteInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2087,13 +2087,13 @@ suite('RemoteInstallAction', () => { const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([languagePackExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2108,15 +2108,15 @@ suite('RemoteInstallAction', () => { const extensionManagementService = instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.RemoteInstallAction, false)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2131,21 +2131,22 @@ suite('RemoteInstallAction', () => { suite('LocalInstallAction', () => { - setup(setupTest); - teardown(() => disposables.dispose()); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => setupTest(disposables)); test('Test local install action is enabled for remote ui extension', async () => { // multi server setup const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2160,13 +2161,13 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2184,15 +2185,15 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const gallery = aGalleryExtension('a', { identifier: remoteUIExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2217,15 +2218,15 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const gallery = aGalleryExtension('a', { identifier: remoteUIExtension.identifier }); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2251,15 +2252,15 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); const localUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localUIExtension], EnablementState.DisabledGlobally); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2273,13 +2274,13 @@ suite('LocalInstallAction', () => { // multi server setup const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2290,13 +2291,13 @@ suite('LocalInstallAction', () => { // multi server setup const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a'))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const pager = await workbenchService.queryGallery(CancellationToken.None); testObject.extension = pager.firstPage[0]; @@ -2314,13 +2315,13 @@ suite('LocalInstallAction', () => { instantiationService.stub(INativeWorkbenchEnvironmentService, environmentService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2334,13 +2335,13 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aSingleRemoteExtensionManagementServerService(instantiationService, createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2355,13 +2356,13 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localUIExtension]), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2375,15 +2376,15 @@ suite('LocalInstallAction', () => { const extensionManagementService = instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [remoteUIExtension]); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2400,13 +2401,13 @@ suite('LocalInstallAction', () => { const remoteUIExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUIExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2419,13 +2420,13 @@ suite('LocalInstallAction', () => { const remoteUISystemExtension = aLocalExtension('a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }), type: ExtensionType.System }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUISystemExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteUISystemExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2438,13 +2439,13 @@ suite('LocalInstallAction', () => { const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: remoteWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2458,13 +2459,13 @@ suite('LocalInstallAction', () => { const remoteWorkspaceExtension = aLocalExtension('a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localWorkspaceExtension]), createExtensionManagementService([remoteWorkspaceExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: localWorkspaceExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; @@ -2477,13 +2478,13 @@ suite('LocalInstallAction', () => { const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([languagePackExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); @@ -2498,15 +2499,15 @@ suite('LocalInstallAction', () => { const extensionManagementService = instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService; const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposables.add(new TestExtensionEnablementService(instantiationService))); const languagePackExtension = aLocalExtension('a', { contributes: { localizations: [{ languageId: 'de', translations: [] }] } }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [languagePackExtension]); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + const workbenchService: IExtensionsWorkbenchService = disposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); instantiationService.set(IExtensionsWorkbenchService, workbenchService); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: languagePackExtension.identifier }))); - const testObject = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); + const testObject = disposables.add(instantiationService.createInstance(ExtensionsActions.LocalInstallAction)); + disposables.add(instantiationService.createInstance(ExtensionContainers, [testObject])); const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts index e5a665e70dd..440111628df 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsViews.test.ts @@ -47,9 +47,12 @@ import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtensionsViews Tests', () => { + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + let instantiationService: TestInstantiationService; let testableView: ExtensionsListView; let installEvent: Emitter, @@ -75,13 +78,13 @@ suite('ExtensionsViews Tests', () => { const fileBasedRecommendationB = aGalleryExtension('filebased-recommendation-B'); const otherRecommendationA = aGalleryExtension('other-recommendation-A'); - suiteSetup(() => { - installEvent = new Emitter(); - didInstallEvent = new Emitter(); - uninstallEvent = new Emitter(); - didUninstallEvent = new Emitter(); + setup(async () => { + installEvent = disposableStore.add(new Emitter()); + didInstallEvent = disposableStore.add(new Emitter()); + uninstallEvent = disposableStore.add(new Emitter()); + didUninstallEvent = disposableStore.add(new Emitter()); - instantiationService = new TestInstantiationService(); + instantiationService = disposableStore.add(new TestInstantiationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IProductService, {}); @@ -122,7 +125,7 @@ suite('ExtensionsViews Tests', () => { } }); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); const reasons: { [key: string]: any } = {}; reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; @@ -163,9 +166,7 @@ suite('ExtensionsViews Tests', () => { } }); instantiationService.stub(IURLService, NativeURLService); - }); - setup(async () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localEnabledTheme, localEnabledLanguage, localRandom, localDisabledTheme, localDisabledLanguage, builtInTheme, builtInBasic]); instantiationService.stubPromise(IExtensionManagementService, 'getExtensgetExtensionsControlManifestionsReport', {}); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); @@ -195,17 +196,8 @@ suite('ExtensionsViews Tests', () => { await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledTheme], EnablementState.DisabledGlobally); await (instantiationService.get(IWorkbenchExtensionEnablementService)).setEnablement([localDisabledLanguage], EnablementState.DisabledGlobally); - instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); - testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }); - }); - - teardown(() => { - (instantiationService.get(IExtensionsWorkbenchService)).dispose(); - testableView.dispose(); - }); - - suiteTeardown(() => { - instantiationService.dispose(); + instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService))); + testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); }); test('Test query types', () => { @@ -513,7 +505,7 @@ suite('ExtensionsViews Tests', () => { }); testableView.dispose(); - testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }); + testableView = disposableStore.add(instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); return testableView.show('search-me').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; @@ -540,7 +532,7 @@ suite('ExtensionsViews Tests', () => { const queryTarget = instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...realResults)); testableView.dispose(); - testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' }); + disposableStore.add(testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' })); return testableView.show('search-me @sort:installs').then(result => { const options: IQueryOptions = queryTarget.args[0][0]; diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index 36450a6c665..91d873c7af0 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -48,28 +48,28 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { platform } from 'vs/base/common/platform'; import { arch } from 'vs/base/common/process'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtensionsWorkbenchServiceTest', () => { let instantiationService: TestInstantiationService; let testObject: IExtensionsWorkbenchService; - const suiteDisposables = new DisposableStore(); - let testDisposables: DisposableStore = new DisposableStore(); + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); let installEvent: Emitter, didInstallEvent: Emitter, uninstallEvent: Emitter, didUninstallEvent: Emitter; - suiteSetup(() => { - suiteDisposables.add(toDisposable(() => sinon.restore())); - installEvent = suiteDisposables.add(new Emitter()); - didInstallEvent = suiteDisposables.add(new Emitter()); - uninstallEvent = suiteDisposables.add(new Emitter()); - didUninstallEvent = suiteDisposables.add(new Emitter()); + setup(async () => { + disposableStore.add(toDisposable(() => sinon.restore())); + installEvent = disposableStore.add(new Emitter()); + didInstallEvent = disposableStore.add(new Emitter()); + uninstallEvent = disposableStore.add(new Emitter()); + didUninstallEvent = disposableStore.add(new Emitter()); - instantiationService = suiteDisposables.add(new TestInstantiationService()); + instantiationService = disposableStore.add(new TestInstantiationService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IProgressService, ProgressService); @@ -115,10 +115,10 @@ suite('ExtensionsWorkbenchServiceTest', () => { extensionManagementService: instantiationService.get(IExtensionManagementService) as IProfileAwareExtensionManagementService, }, null, null)); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); - instantiationService.stub(ILifecycleService, new TestLifecycleService()); - instantiationService.stub(IExtensionTipsService, instantiationService.createInstance(TestExtensionTipsService)); + instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService())); + instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService))); instantiationService.stub(IExtensionRecommendationsService, {}); instantiationService.stub(INotificationService, { prompt: () => null! }); @@ -128,12 +128,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { extensions: [], async whenInstalledExtensionsRegistered() { return true; } }); - }); - suiteTeardown(() => suiteDisposables.dispose()); - - setup(async () => { - testDisposables = new DisposableStore(); instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); @@ -142,8 +137,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { (instantiationService.get(IWorkbenchExtensionEnablementService)).reset(); }); - teardown(() => testDisposables.dispose()); - test('test gallery extension', async () => { const expected = aGalleryExtension('expectedName', { displayName: 'expectedDisplayName', @@ -325,7 +318,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); await testObject.queryLocal(); - return eventToPromise(testObject.onChange).then(() => { + return Event.toPromise(testObject.onChange).then(() => { const actuals = testObject.local; assert.strictEqual(2, actuals.length); @@ -448,7 +441,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject = await aWorkbenchService(); const target = testObject.local[0]; - await eventToPromise(Event.filter(testObject.onChange, e => !!e?.gallery)); + await Event.toPromise(Event.filter(testObject.onChange, e => !!e?.gallery)); assert.ok(await testObject.canInstall(target)); }); @@ -482,7 +475,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.strictEqual(ExtensionState.Uninstalled, extension.state); testObject.install(extension); - testObject.onChange(target); + disposableStore.add(testObject.onChange(target)); // Installing installEvent.fire({ identifier: gallery.identifier, source: gallery }); @@ -498,7 +491,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const target = sinon.spy(); testObject.uninstall(testObject.local[0]); - testObject.onChange(target); + disposableStore.add(testObject.onChange(target)); uninstallEvent.fire({ identifier: local.identifier }); assert.ok(target.calledOnce); @@ -512,7 +505,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { testObject.uninstall(testObject.local[0]); uninstallEvent.fire({ identifier: local.identifier }); - testObject.onChange(target); + disposableStore.add(testObject.onChange(target)); didUninstallEvent.fire({ identifier: local.identifier }); assert.ok(target.calledOnce); @@ -996,7 +989,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]); testObject = await aWorkbenchService(); const target = sinon.spy(); - testObject.onChange(target); + disposableStore.add(testObject.onChange(target)); return testObject.setEnablement(testObject.local[0], EnablementState.DisabledGlobally) .then(() => assert.ok(target.calledOnce)); @@ -1011,7 +1004,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localExtension]); testObject = await aWorkbenchService(); const target = sinon.spy(); - testObject.onChange(target); + disposableStore.add(testObject.onChange(target)); return instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally) .then(() => assert.ok(target.calledOnce)); @@ -1071,7 +1064,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1087,7 +1080,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1103,7 +1096,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1119,7 +1112,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1135,7 +1128,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1151,7 +1144,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1167,7 +1160,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1183,7 +1176,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1199,7 +1192,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1215,7 +1208,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1231,7 +1224,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1248,7 +1241,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1265,7 +1258,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1281,7 +1274,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1298,7 +1291,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1315,7 +1308,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([remoteExtension], EnablementState.DisabledGlobally); testObject = await aWorkbenchService(); @@ -1334,7 +1327,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([remoteExtension], EnablementState.DisabledWorkspace); testObject = await aWorkbenchService(); @@ -1353,7 +1346,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledGlobally); testObject = await aWorkbenchService(); @@ -1372,7 +1365,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); await instantiationService.get(IWorkbenchExtensionEnablementService).setEnablement([localExtension], EnablementState.DisabledWorkspace); testObject = await aWorkbenchService(); @@ -1391,7 +1384,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([localExtension]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1407,7 +1400,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService([]), createExtensionManagementService([remoteExtension])); instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService))); testObject = await aWorkbenchService(); const actual = await testObject.queryLocal(); @@ -1417,7 +1410,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); async function aWorkbenchService(): Promise { - const workbenchService: ExtensionsWorkbenchService = testDisposables.add(instantiationService.createInstance(ExtensionsWorkbenchService)); + const workbenchService: ExtensionsWorkbenchService = disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)); await workbenchService.queryLocal(); return workbenchService; } @@ -1458,17 +1451,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! }; } - function eventToPromise(event: Event, count: number = 1): Promise { - return new Promise(c => { - let counter = 0; - event(() => { - if (++counter === count) { - c(undefined); - } - }); - }); - } - function aMultiExtensionManagementServerService(instantiationService: TestInstantiationService, localExtensionManagementService?: IProfileAwareExtensionManagementService, remoteExtensionManagementService?: IProfileAwareExtensionManagementService): IExtensionManagementServerService { const localExtensionManagementServer: IExtensionManagementServer = { id: 'vscode-local', diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 1d8cacb2985..30c8d290a47 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,6 +39,7 @@ import { Progress } from 'vs/platform/progress/common/progress'; import { generateUuid } from 'vs/base/common/uuid'; import { TextEdit } from 'vs/editor/common/languages'; import { ISelection } from 'vs/editor/common/core/selection'; +import { onUnexpectedError } from 'vs/base/common/errors'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -142,7 +143,7 @@ export class InlineChatController implements IEditorContribution { } this._log('session RESUMING', e); - await this._nextState(State.CREATE_SESSION, { existingSession }); + await this.run({ existingSession }); this._log('session done or paused'); })); this._log('NEW controller'); @@ -151,7 +152,9 @@ export class InlineChatController implements IEditorContribution { dispose(): void { this._strategy?.dispose(); this._stashedSession.clear(); - this.finishExistingSession(); + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } this._store.dispose(); this._log('controller disposed'); } @@ -189,17 +192,29 @@ export class InlineChatController implements IEditorContribution { private _currentRun?: Promise; async run(options: InlineChatRunOptions | undefined = {}): Promise { - this.finishExistingSession(); - if (this._currentRun) { + try { + this.finishExistingSession(); + if (this._currentRun) { + await this._currentRun; + } + this._stashedSession.clear(); + if (options.initialSelection) { + this._editor.setSelection(options.initialSelection); + } + this._currentRun = this._nextState(State.CREATE_SESSION, options); await this._currentRun; + + } catch (error) { + // this should not happen but when it does make sure to tear down the UI and everything + onUnexpectedError(error); + if (this._activeSession) { + this._inlineChatSessionService.releaseSession(this._activeSession); + } + this[State.PAUSE](); + + } finally { + this._currentRun = undefined; } - this._stashedSession.clear(); - if (options.initialSelection) { - this._editor.setSelection(options.initialSelection); - } - this._currentRun = this._nextState(State.CREATE_SESSION, options); - await this._currentRun; - this._currentRun = undefined; } // ---- state machine @@ -543,6 +558,7 @@ export class InlineChatController implements IEditorContribution { this._ctxHasActiveRequest.set(false); this._zone.value.widget.updateProgress(false); this._zone.value.widget.updateInfo(''); + this._zone.value.widget.updateToolbar(true); this._log('request took', sw.elapsed(), this._activeSession.provider.debugName); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 0f153ca64da..2d2a2a7a5bb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -170,9 +170,15 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { return; } - // complex changes - this._logService.debug('[IE] livePreview-mode: full diff'); - this._renderChangesWithFullDiff(changes, range); + if (changes.length === 0 || this._session.textModel0.getValueLength() === 0) { + // no change or changes to an empty file + this._logService.debug('[IE] livePreview-mode: no diff'); + this._cleanupFullDiff(); + } else { + // complex changes + this._logService.debug('[IE] livePreview-mode: full diff'); + this._renderChangesWithFullDiff(changes, range); + } } // --- full diff diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 76944f5b38e..5ffa49b8217 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -409,7 +409,7 @@ export class LivePreviewStrategy extends LiveStrategy { } if (response.singleCreateFileEdit) { - this._previewZone.value.showCreation(this._session.wholeRange.value, response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits)); + this._previewZone.value.showCreation(this._session.wholeRange.value.collapseToEnd(), response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits)); } else { this._previewZone.value.hide(); } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 0c40a38925a..8643d6c62c9 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -34,7 +34,6 @@ export interface IInlineChatSession { message?: string; slashCommands?: IInlineChatSlashCommand[]; wholeRange?: IRange; - dispose?(): void; } export interface IInlineChatRequest { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index e31040fecc5..806f24b91e4 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -8,7 +8,10 @@ import { equals } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { TestDiffProviderFactoryService } from 'vs/editor/browser/diff/testDiffProviderFactoryService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; @@ -30,7 +33,6 @@ import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/in import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('InteractiveChatController', function () { - class TestController extends InlineChatController { static INIT_SEQUENCE: readonly State[] = [State.CREATE_SESSION, State.INIT_UI, State.WAIT_FOR_INPUT]; @@ -92,6 +94,7 @@ suite('InteractiveChatController', function () { const serviceCollection = new ServiceCollection( [IContextKeyService, contextKeyService], [IInlineChatService, inlineChatService], + [IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)], [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionService)], [IEditorProgressService, new class extends mock() { override show(total: unknown, delay?: unknown): IProgressRunner { @@ -145,8 +148,7 @@ suite('InteractiveChatController', function () { ctrl?.dispose(); }); - // todo: re-enable this when earlier tests are fixed - // ensureNoDisposablesAreLeakedInTestSuite(); + ensureNoDisposablesAreLeakedInTestSuite(); test('creation, not showing anything', function () { ctrl = instaService.createInstance(TestController, editor); @@ -229,7 +231,7 @@ suite('InteractiveChatController', function () { test('typing outside of wholeRange finishes session', async function () { ctrl = instaService.createInstance(TestController, editor); const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); - ctrl.run({ message: 'Hello', autoSend: true }); + const r = ctrl.run({ message: 'Hello', autoSend: true }); await p; @@ -241,6 +243,7 @@ suite('InteractiveChatController', function () { editor.trigger('test', 'type', { text: 'a' }); await ctrl.waitFor([State.ACCEPT]); + await r; }); test('\'whole range\' isn\'t updated for edits outside whole range #4346', async function () { @@ -270,7 +273,7 @@ suite('InteractiveChatController', function () { store.add(d); ctrl = instaService.createInstance(TestController, editor); const p = ctrl.waitFor(TestController.INIT_SEQUENCE); - ctrl.run({ message: 'Hello', autoSend: false }); + const r = ctrl.run({ message: 'Hello', autoSend: false }); await p; @@ -283,6 +286,9 @@ suite('InteractiveChatController', function () { await ctrl.waitFor([State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 12)); + + ctrl.cancelSession(); + await r; }); test('Stuck inline chat widget #211', async function () { diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index afa4414c3c0..90dbcd2f771 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -53,6 +53,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { CellEditType, CellKind, CellUri, INTERACTIVE_WINDOW_EDITOR_ID, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { InteractiveWindowOpen } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -699,7 +700,11 @@ registerAction2(class extends Action2 { id: 'interactive.input.focus', title: { value: localize('interactive.input.focus', "Focus Input Editor"), original: 'Focus Input Editor' }, category: interactiveWindowCategory, - f1: true + menu: { + id: MenuId.CommandPalette, + when: InteractiveWindowOpen, + }, + precondition: InteractiveWindowOpen, }); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts deleted file mode 100644 index 01ee19a1ff9..00000000000 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelProjection.ts +++ /dev/null @@ -1,157 +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 { ArrayQueue } from 'vs/base/common/arrays'; -import { BugIndicatingError } from 'vs/base/common/errors'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { Position } from 'vs/editor/common/core/position'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; - -export class TextModelProjection extends Disposable { - private static counter: number = 0; - - public static create( - sourceDocument: ITextModel, - projectionConfiguration: ProjectionConfiguration, - modelService: IModelService - ): TextModelProjection { - const textModel = TextModelProjection.createModelReference( - modelService - ); - return new TextModelProjection(textModel, sourceDocument, { dispose: () => { } }, projectionConfiguration); - } - - public static createForTargetDocument( - sourceDocument: ITextModel, - projectionConfiguration: ProjectionConfiguration, - targetDocument: ITextModel, - ): TextModelProjection { - return new TextModelProjection(targetDocument, sourceDocument, new DisposableStore(), projectionConfiguration); - } - - private static createModelReference( - modelService: IModelService - ): ITextModel { - const uri = URI.from({ - scheme: 'projected-text-model', - path: `/projection${TextModelProjection.counter++}`, - }); - - return modelService.createModel('', null, uri, false); - } - - private currentBlocks: Block[]; - - constructor( - public readonly targetDocument: ITextModel, - private readonly sourceDocument: ITextModel, - disposable: IDisposable, - projectionConfiguration: ProjectionConfiguration - ) { - super(); - - this._register(disposable); - - const result = getBlocks(sourceDocument, projectionConfiguration); - this.currentBlocks = result.blocks; - targetDocument.setValue(result.transformedContent); - - this._register( - sourceDocument.onDidChangeContent((c) => { - // TODO improve this - const result = getBlocks(sourceDocument, projectionConfiguration); - this.currentBlocks = result.blocks; - targetDocument.setValue(result.transformedContent); - }) - ); - } - - /** - * The created transformer can only be called with monotonically increasing positions. - */ - createMonotonousReverseTransformer(): Transformer { - let lineDelta = 0; - const blockQueue = new ArrayQueue(this.currentBlocks); - let lastLineNumber = 0; - const sourceDocument = this.sourceDocument; - return { - transform(position) { - if (position.lineNumber < lastLineNumber) { - throw new BugIndicatingError(); - } - lastLineNumber = position.lineNumber; - - while (true) { - const next = blockQueue.peek(); - if (!next) { - break; - } - if (position.lineNumber + lineDelta > next.lineRange.startLineNumber) { - blockQueue.dequeue(); - lineDelta += next.lineRange.lineCount - 1; - } else if (position.lineNumber + lineDelta === next.lineRange.startLineNumber && position.column === 2) { - const targetLineNumber = position.lineNumber + lineDelta + next.lineRange.lineCount - 1; - return new Position(targetLineNumber, sourceDocument.getLineMaxColumn(targetLineNumber)); - } else { - break; - } - } - - // Column number never changes - return new Position(position.lineNumber + lineDelta, position.column); - }, - }; - } -} - -function getBlocks(document: ITextModel, configuration: ProjectionConfiguration): { blocks: Block[]; transformedContent: string } { - const blocks: Block[] = []; - const transformedContent: string[] = []; - - let inBlock = false; - let startLineNumber = -1; - let curLine = 0; - - for (const line of document.getLinesContent()) { - curLine++; - if (!inBlock) { - if (line.startsWith(configuration.blockToRemoveStartLinePrefix)) { - inBlock = true; - startLineNumber = curLine; - } else { - transformedContent.push(line); - } - } else { - if (line.startsWith(configuration.blockToRemoveEndLinePrefix)) { - inBlock = false; - blocks.push(new Block(new LineRange(startLineNumber, curLine - startLineNumber + 1))); - // We add a (hopefully) unique symbol so that diffing recognizes the deleted block (HEXAGRAM FOR CONFLICT) - // allow-any-unicode-next-line - transformedContent.push('䷅'); - } - } - } - - return { - blocks, - transformedContent: transformedContent.join('\n') - }; -} - -class Block { - constructor(public readonly lineRange: LineRange) { } -} - -interface ProjectionConfiguration { - blockToRemoveStartLinePrefix: string; - blockToRemoveEndLinePrefix: string; -} - -interface Transformer { - transform(position: Position): Position; -} diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts deleted file mode 100644 index 1020861d913..00000000000 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/projection.test.ts +++ /dev/null @@ -1,68 +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 { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { Position } from 'vs/editor/common/core/position'; -import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { TextModelProjection } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelProjection'; - -suite('TextModelProjection', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('Basic', () => { - const source = createTextModel(` -1 this.container.appendChild(this.labelContainer); -2 -3 // Beak Container -4 this.beakContainer = document.createElement('div'); -<<<<<<< .\input1.ts -this.beakContainer.className = 'status-bar-item-beak-container'; -======= -this.beakContainer.className = 'status-bar-beak-container'; - -// Add to parent ->>>>>>> .\input2.ts -5 this.container.appendChild(this.beakContainer); -6 -7 this.update(entry); -`); - const target = createTextModel(''); - - const projection = TextModelProjection.createForTargetDocument(source, { blockToRemoveStartLinePrefix: '<<<<<<<', blockToRemoveEndLinePrefix: '>>>>>>>' }, target); - - assert.deepStrictEqual(target.getValue(), ` -1 this.container.appendChild(this.labelContainer); -2 -3 // Beak Container -4 this.beakContainer = document.createElement('div'); -䷅ -5 this.container.appendChild(this.beakContainer); -6 -7 this.update(entry); -`); - - const transformer = projection.createMonotonousReverseTransformer(); - const lineNumbers = target.getLinesContent().map((l, idx) => idx + 1); - const transformedLineNumbers = lineNumbers.map(n => transformer.transform(new Position(n, 1))); - - assert.deepStrictEqual(transformedLineNumbers.map(l => l.lineNumber), [ - 1, - 2, - 3, - 4, - 5, - 6, - 13, - 14, - 15, - 16, - ]); - - projection.dispose(); - source.dispose(); - target.dispose(); - }); -}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts new file mode 100644 index 00000000000..3948fef3117 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { EmptyTextEditorHintContribution } from 'vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint'; +import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribution { + public static readonly CONTRIB_ID = 'notebook.editor.contrib.emptyCellEditorHint'; + constructor( + editor: ICodeEditor, + @IEditorService private readonly _editorService: IEditorService, + @IEditorGroupsService editorGroupsService: IEditorGroupsService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, + @IInlineChatService inlineChatService: IInlineChatService, + @ITelemetryService telemetryService: ITelemetryService, + @IProductService productService: IProductService + ) { + super( + editor, + editorGroupsService, + commandService, + configurationService, + keybindingService, + inlineChatSessionService, + inlineChatService, + telemetryService, + productService + ); + + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + + if (!activeEditor) { + return; + } + + this.toDispose.push(activeEditor.onDidChangeActiveCell(() => this.update())); + } + + protected override _shouldRenderHint(): boolean { + const shouldRenderHint = super._shouldRenderHint(); + if (!shouldRenderHint) { + return false; + } + + const model = this.editor.getModel(); + if (!model) { + return false; + } + + const isNotebookCell = model?.uri.scheme === Schemas.vscodeNotebookCell; + if (!isNotebookCell) { + return false; + } + + const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + if (!activeEditor) { + return false; + } + + const activeCell = activeEditor.getActiveCell(); + + if (activeCell?.uri.fragment !== model.uri.fragment) { + return false; + } + + return true; + } +} + +registerEditorContribution(EmptyCellEditorHintContribution.CONTRIB_ID, EmptyCellEditorHintContribution, EditorContributionInstantiation.Eager); // eager because it needs to render a help message diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index cf6589b6027..96903926179 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -28,12 +28,13 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IEditorPane } from 'vs/workbench/common/editor'; import { CellRevealType, INotebookEditorOptions, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigCollapseItemsValues, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; - +import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; +import { CancellationToken } from 'vs/base/common/cancellation'; class NotebookOutlineTemplate { @@ -156,14 +157,16 @@ class NotebookQuickPickProvider implements IQuickPickDataSource { } const result: IQuickPickOutlineElement[] = []; const { hasFileIcons } = this._themeService.getFileIconTheme(); + for (const element of bucket) { + const useFileIcon = hasFileIcons && !element.symbolKind; // todo@jrieken it is fishy that codicons cannot be used with iconClasses // but file icons can... result.push({ element, - label: hasFileIcons ? element.label : `$(${element.icon.id}) ${element.label}`, + label: useFileIcon ? element.label : `$(${element.icon.id}) ${element.label}`, ariaLabel: element.label, - iconClasses: hasFileIcons ? getIconClassesForLanguageId(element.cell.language ?? '') : undefined, + iconClasses: useFileIcon ? getIconClassesForLanguageId(element.cell.language ?? '') : undefined, }); } return result; @@ -273,6 +276,10 @@ export class NotebookCellOutline implements IOutline { }; } + async setFullSymbols(cancelToken: CancellationToken) { + await this._outlineProvider?.setFullSymbols(cancelToken); + } + get uri(): URI | undefined { return this._outlineProvider?.uri; } @@ -285,7 +292,8 @@ export class NotebookCellOutline implements IOutline { options: { ...options, override: this._editor.input?.editorId, - cellRevealType: CellRevealType.NearTopIfOutsideViewport + cellRevealType: CellRevealType.NearTopIfOutsideViewport, + selection: entry.position } as INotebookEditorOptions, }, sideBySide ? SIDE_GROUP : undefined); } @@ -330,6 +338,7 @@ export class NotebookOutlineCreator implements IOutlineCreator reg.dispose(); @@ -339,8 +348,14 @@ export class NotebookOutlineCreator implements IOutlineCreator | undefined> { - return this._instantiationService.createInstance(NotebookCellOutline, editor, target); + async createOutline(editor: NotebookEditor, target: OutlineTarget, cancelToken: CancellationToken): Promise | undefined> { + const outline = this._instantiationService.createInstance(NotebookCellOutline, editor, target); + + const showAllSymbols = this._configurationService.getValue(NotebookSetting.gotoSymbolsAllSymbols); + if (target === OutlineTarget.QuickPick && showAllSymbols) { + await outline.setFullSymbols(cancelToken); + } + return outline; } } @@ -362,5 +377,10 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: true, markdownDescription: localize('breadcrumbs.showCodeCells', "When enabled notebook breadcrumbs contain code cells.") }, + [NotebookSetting.gotoSymbolsAllSymbols]: { + type: 'boolean', + default: false, + markdownDescription: localize('notebook.gotoSymbols.showAllSymbols', "When enabled goto symbol quickpick will display full code symbols from the notebook, as well as markdown headers.") + }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts index 3e85f092688..0f31bdfc6ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel.ts @@ -84,7 +84,8 @@ export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCe this._outputViewModels = this.textModel.outputs.map(output => new CellOutputViewModel(this, output, this._notebookService)); this._register(this.textModel.onDidChangeOutputs((splice) => { this._outputCollection.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(() => 0)); - this._outputViewModels.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(output => new CellOutputViewModel(this, output, this._notebookService))); + const removed = this._outputViewModels.splice(splice.start, splice.deleteCount, ...splice.newOutputs.map(output => new CellOutputViewModel(this, output, this._notebookService))); + removed.forEach(vm => vm.dispose()); this._outputsTop = null; this._onDidChangeOutputLayout.fire(); @@ -130,4 +131,12 @@ export class DiffNestedCellViewModel extends Disposable implements IDiffNestedCe return this._outputsTop?.getTotalSum() ?? 0; } + + public override dispose(): void { + super.dispose(); + + this._outputViewModels.forEach(output => { + output.dispose(); + }); + } } diff --git a/src/vscode-dts/vscode.proposed.envShellEvent.d.ts b/src/vs/workbench/contrib/notebook/browser/media/notebookCellEditorHint.css similarity index 59% rename from src/vscode-dts/vscode.proposed.envShellEvent.d.ts rename to src/vs/workbench/contrib/notebook/browser/media/notebookCellEditorHint.css index 8fed971ef71..b9ce49783e8 100644 --- a/src/vscode-dts/vscode.proposed.envShellEvent.d.ts +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookCellEditorHint.css @@ -3,14 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'vscode' { - - // See https://github.com/microsoft/vscode/issues/160694 - export namespace env { - - /** - * An {@link Event} which fires when the default shell changes. - */ - export const onDidChangeShell: Event; - } +.monaco-workbench .notebookOverlay .monaco-editor .contentWidgets .empty-editor-hint { + cursor: auto; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 2ef3dfb4e8e..6372ee6dada 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -69,6 +69,7 @@ import 'vs/workbench/contrib/notebook/browser/controller/apiActions'; import 'vs/workbench/contrib/notebook/browser/controller/foldingController'; // Editor Contribution +import 'vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint'; import 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard'; import 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFind'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; @@ -118,7 +119,6 @@ import { runAccessibilityHelpAction, showAccessibleOutput } from 'vs/workbench/c import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; - /*--------------------------------------------------------------------------------------------- */ Registry.as(EditorExtensions.EditorPane).registerEditorPane( diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 3830664287b..9269abcf748 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notebook'; +import 'vs/css!./media/notebookCellEditorHint'; import 'vs/css!./media/notebookCellInsertToolbar'; import 'vs/css!./media/notebookCellStatusBar'; import 'vs/css!./media/notebookCellTitleToolbar'; @@ -112,7 +113,8 @@ export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOpti 'editor.contrib.testingOutputPeek', 'editor.contrib.testingDecorations', 'store.contrib.stickyScrollController', - 'editor.contrib.findController' + 'editor.contrib.findController', + 'editor.contrib.emptyTextEditorHint' ]; const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1); diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts index e61cfbe8181..e5baf7c6af3 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts @@ -12,9 +12,12 @@ import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbenc import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Emitter } from 'vs/base/common/event'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { GroupIdentifier, GroupModelChangeKind } from 'vs/workbench/common/editor'; import { Dimension } from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { InteractiveWindowOpen } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; export class NotebookEditorWidgetService implements INotebookEditorService { @@ -34,6 +37,8 @@ export class NotebookEditorWidgetService implements INotebookEditorService { constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService ) { const groupListener = new Map(); @@ -90,6 +95,19 @@ export class NotebookEditorWidgetService implements INotebookEditorService { } } })); + + const interactiveWindowOpen = InteractiveWindowOpen.bindTo(contextKeyService); + this._disposables.add(editorService.onDidEditorsChange(e => { + if (e.event.kind === GroupModelChangeKind.EDITOR_OPEN && !interactiveWindowOpen.get()) { + if (editorService.editors.find(editor => editor.editorId === 'interactive')) { + interactiveWindowOpen.set(true); + } + } else if (e.event.kind === GroupModelChangeKind.EDITOR_CLOSE && interactiveWindowOpen.get()) { + if (!editorService.editors.find(editor => editor.editorId === 'interactive')) { + interactiveWindowOpen.set(false); + } + } + })); } dispose() { diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts index 67d4972cced..9ca31a78982 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl.ts @@ -7,8 +7,6 @@ import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; -import { joinPath } from 'vs/base/common/resources'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const logChannelId = 'notebook.rendering'; @@ -20,18 +18,15 @@ export class NotebookLoggingService extends Disposable implements INotebookLoggi constructor( @ILoggerService loggerService: ILoggerService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { super(); - const logsPath = joinPath(environmentService.windowLogsPath, 'notebook.rendering.log'); - this._logger = this._register(loggerService.createLogger(logsPath, { id: logChannelId, name: nls.localize('renderChannelName', "Notebook rendering") })); + this._logger = this._register(loggerService.createLogger(logChannelId, { name: nls.localize('renderChannelName', "Notebook rendering") })); } debug(category: string, output: string): void { this._logger.debug(`[${category}] ${output}`); } - info(category: string, output: string): void { this._logger.info(`[${category}] ${output}`); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts new file mode 100644 index 00000000000..9fd1c4f7160 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { Range } from 'vs/editor/common/core/range'; +import { SymbolKind, SymbolKinds } from 'vs/editor/common/languages'; + + +export interface IOutlineMarkerInfo { + readonly count: number; + readonly topSev: MarkerSeverity; +} + +export class OutlineEntry { + private _children: OutlineEntry[] = []; + private _parent: OutlineEntry | undefined; + private _markerInfo: IOutlineMarkerInfo | undefined; + + get icon(): ThemeIcon { + if (this.symbolKind) { + return SymbolKinds.toIcon(this.symbolKind); + } + return this.isExecuting && this.isPaused ? executingStateIcon : + this.isExecuting ? ThemeIcon.modify(executingStateIcon, 'spin') : + this.cell.cellKind === CellKind.Markup ? Codicon.markdown : Codicon.code; + } + + constructor( + readonly index: number, + readonly level: number, + readonly cell: ICellViewModel, + readonly label: string, + readonly isExecuting: boolean, + readonly isPaused: boolean, + readonly position?: Range, + readonly symbolKind?: SymbolKind, + ) { } + + addChild(entry: OutlineEntry) { + this._children.push(entry); + entry._parent = this; + } + + get parent(): OutlineEntry | undefined { + return this._parent; + } + + get children(): Iterable { + return this._children; + } + + get markerInfo(): IOutlineMarkerInfo | undefined { + return this._markerInfo; + } + + updateMarkers(markerService: IMarkerService): void { + if (this.cell.cellKind === CellKind.Code) { + // a code cell can have marker + const marker = markerService.read({ resource: this.cell.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); + if (marker.length === 0) { + this._markerInfo = undefined; + } else { + const topSev = marker.find(a => a.severity === MarkerSeverity.Error)?.severity ?? MarkerSeverity.Warning; + this._markerInfo = { topSev, count: marker.length }; + } + } else { + // a markdown cell can inherit markers from its children + let topChild: MarkerSeverity | undefined; + for (const child of this.children) { + child.updateMarkers(markerService); + if (child.markerInfo) { + topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); + } + } + this._markerInfo = topChild && { topSev: topChild, count: 0 }; + } + } + + clearMarkers(): void { + this._markerInfo = undefined; + for (const child of this.children) { + child.clearMarkers(); + } + } + + find(cell: ICellViewModel, parents: OutlineEntry[]): OutlineEntry | undefined { + if (cell.id === this.cell.id) { + return this; + } + parents.push(this); + for (const child of this.children) { + const result = child.find(cell, parents); + if (result) { + return result; + } + } + parents.pop(); + return undefined; + } + + asFlatList(bucket: OutlineEntry[]): void { + bucket.push(this); + for (const child of this.children) { + child.asFlatList(bucket); + } + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts new file mode 100644 index 00000000000..0671f846db6 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IOutlineModelService, OutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; +import { localize } from 'vs/nls'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { getMarkdownHeadersInCell } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; +import { OutlineEntry } from './OutlineEntry'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import { SymbolKind } from 'vs/editor/common/languages'; + +type entryDesc = { + name: string; + position: Range; + level: number; + kind: SymbolKind; +}; + +export class NotebookOutlineEntryFactory { + + private cellOutlineEntryCache: Record = {}; + + constructor( + private readonly executionStateService: INotebookExecutionStateService + ) { } + + public getOutlineEntries(cell: ICellViewModel, index: number): OutlineEntry[] { + const entries: OutlineEntry[] = []; + + const isMarkdown = cell.cellKind === CellKind.Markup; + + // cap the amount of characters that we look at and use the following logic + // - for MD prefer headings (each header is an entry) + // - otherwise use the first none-empty line of the cell (MD or code) + let content = getCellFirstNonEmptyLine(cell); + let hasHeader = false; + + if (isMarkdown) { + const fullContent = cell.getText().substring(0, 10000); + for (const { depth, text } of getMarkdownHeadersInCell(fullContent)) { + hasHeader = true; + entries.push(new OutlineEntry(index++, depth, cell, text, false, false)); + } + + if (!hasHeader) { + // no markdown syntax headers, try to find html tags + const match = fullContent.match(/(.*)<\/h\1>/i); + if (match) { + hasHeader = true; + const level = parseInt(match[1]); + const text = match[2].trim(); + entries.push(new OutlineEntry(index++, level, cell, text, false, false)); + } + } + + if (!hasHeader) { + content = renderMarkdownAsPlaintext({ value: content }); + } + } + + if (!hasHeader) { + if (!isMarkdown && cell.model.textModel) { + const cachedEntries = this.cellOutlineEntryCache[cell.model.textModel.id]; + + // Gathering symbols from the model is an async operation, but this provider is syncronous. + // So symbols need to be precached before this function is called to get the full list. + if (cachedEntries) { + cachedEntries.forEach((cached) => { + entries.push(new OutlineEntry(index++, cached.level, cell, cached.name, false, false, cached.position, cached.kind)); + }); + + } + } + + const exeState = !isMarkdown && this.executionStateService.getCellExecution(cell.uri); + if (entries.length === 0) { + let preview = content.trim(); + if (preview.length === 0) { + // empty or just whitespace + preview = localize('empty', "empty cell"); + } + + entries.push(new OutlineEntry(index++, 7, cell, preview, !!exeState, exeState ? exeState.isPaused : false)); + } + } + + return entries; + } + + public async cacheSymbols(textModel: ITextModel, outlineModelService: IOutlineModelService, cancelToken: CancellationToken) { + const outlineModel = await outlineModelService.getOrCreate(textModel, cancelToken); + const entries = createOutlineEntries(outlineModel.getTopLevelSymbols(), 7); + this.cellOutlineEntryCache[textModel.id] = entries; + } +} + +type outlineModel = Awaited>; +type documentSymbol = ReturnType[number]; + +function createOutlineEntries(symbols: documentSymbol[], level: number): entryDesc[] { + const entries: entryDesc[] = []; + symbols.forEach(symbol => { + const position = new Range(symbol.selectionRange.startLineNumber, + symbol.selectionRange.startColumn, + symbol.selectionRange.startLineNumber, + symbol.selectionRange.startColumn); + entries.push({ name: symbol.name, position, level, kind: symbol.kind }); + if (symbol.children) { + entries.push(...createOutlineEntries(symbol.children, level + 1)); + } + }); + return entries; +} + +function getCellFirstNonEmptyLine(cell: ICellViewModel) { + const textBuffer = cell.textBuffer; + for (let i = 0; i < textBuffer.getLineCount(); i++) { + const firstNonWhitespace = textBuffer.getLineFirstNonWhitespaceColumn(i + 1); + const lineLength = textBuffer.getLineLength(i + 1); + if (firstNonWhitespace < lineLength) { + return textBuffer.getLineContent(i + 1); + } + } + + return cell.getText().substring(0, 100); +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts index 47971ec803e..7e5f1d22b66 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts @@ -3,120 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; -import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IActiveNotebookEditor, ICellViewModel, INotebookEditor, INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { getMarkdownHeadersInCell } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; +import { IActiveNotebookEditor, INotebookEditor, INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; - -export interface IOutlineMarkerInfo { - readonly count: number; - readonly topSev: MarkerSeverity; -} - -export class OutlineEntry { - private _children: OutlineEntry[] = []; - private _parent: OutlineEntry | undefined; - private _markerInfo: IOutlineMarkerInfo | undefined; - - get icon(): ThemeIcon { - return this.isExecuting && this.isPaused ? executingStateIcon : - this.isExecuting ? ThemeIcon.modify(executingStateIcon, 'spin') : - this.cell.cellKind === CellKind.Markup ? Codicon.markdown : Codicon.code; - } - - constructor( - readonly index: number, - readonly level: number, - readonly cell: ICellViewModel, - readonly label: string, - readonly isExecuting: boolean, - readonly isPaused: boolean - ) { } - - addChild(entry: OutlineEntry) { - this._children.push(entry); - entry._parent = this; - } - - get parent(): OutlineEntry | undefined { - return this._parent; - } - - get children(): Iterable { - return this._children; - } - - get markerInfo(): IOutlineMarkerInfo | undefined { - return this._markerInfo; - } - - updateMarkers(markerService: IMarkerService): void { - if (this.cell.cellKind === CellKind.Code) { - // a code cell can have marker - const marker = markerService.read({ resource: this.cell.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); - if (marker.length === 0) { - this._markerInfo = undefined; - } else { - const topSev = marker.find(a => a.severity === MarkerSeverity.Error)?.severity ?? MarkerSeverity.Warning; - this._markerInfo = { topSev, count: marker.length }; - } - } else { - // a markdown cell can inherit markers from its children - let topChild: MarkerSeverity | undefined; - for (const child of this.children) { - child.updateMarkers(markerService); - if (child.markerInfo) { - topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); - } - } - this._markerInfo = topChild && { topSev: topChild, count: 0 }; - } - } - - clearMarkers(): void { - this._markerInfo = undefined; - for (const child of this.children) { - child.clearMarkers(); - } - } - - find(cell: ICellViewModel, parents: OutlineEntry[]): OutlineEntry | undefined { - if (cell.id === this.cell.id) { - return this; - } - parents.push(this); - for (const child of this.children) { - const result = child.find(cell, parents); - if (result) { - return result; - } - } - parents.pop(); - return undefined; - } - - asFlatList(bucket: OutlineEntry[]): void { - bucket.push(this); - for (const child of this.children) { - child.asFlatList(bucket); - } - } -} - +import { OutlineEntry } from './OutlineEntry'; +import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; export class NotebookCellOutlineProvider { private readonly _dispoables = new DisposableStore(); @@ -139,15 +40,19 @@ export class NotebookCellOutlineProvider { return this._activeEntry; } + private readonly _outlineEntryFactory: NotebookOutlineEntryFactory; + constructor( private readonly _editor: INotebookEditor, private readonly _target: OutlineTarget, @IThemeService themeService: IThemeService, - @IEditorService _editorService: IEditorService, + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, + @IOutlineModelService private readonly _outlineModelService: IOutlineModelService, @IMarkerService private readonly _markerService: IMarkerService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, ) { + this._outlineEntryFactory = new NotebookOutlineEntryFactory(notebookExecutionStateService); + const selectionListener = new MutableDisposable(); this._dispoables.add(selectionListener); @@ -174,7 +79,7 @@ export class NotebookCellOutlineProvider { this._onDidChange.fire({}); })); - this._dispoables.add(_notebookExecutionStateService.onDidChangeExecution(e => { + this._dispoables.add(notebookExecutionStateService.onDidChangeExecution(e => { if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { this._recomputeState(); } @@ -194,10 +99,29 @@ export class NotebookCellOutlineProvider { this._recomputeState(); } + async setFullSymbols(cancelToken: CancellationToken) { + const notebookEditorWidget = this._editor; + + const notebookCells = notebookEditorWidget?.getViewModel()?.viewCells.filter((cell) => cell.cellKind === CellKind.Code); + + this._entries.length = 0; + if (notebookCells) { + const promises: Promise[] = []; + for (const cell of notebookCells) { + if (cell.textModel) { + // gather all symbols asynchronously + promises.push(this._outlineEntryFactory.cacheSymbols(cell.textModel, this._outlineModelService, cancelToken)); + } + } + await Promise.allSettled(promises); + } + + this._recomputeState(); + } + private _recomputeState(): void { this._entriesDisposables.clear(); this._activeEntry = undefined; - this._entries.length = 0; this._uri = undefined; if (!this._editor.hasModel()) { @@ -219,61 +143,11 @@ export class NotebookCellOutlineProvider { includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); } - const focusedCellIndex = notebookEditorWidget.getFocus().start; - const focused = notebookEditorWidget.cellAt(focusedCellIndex)?.handle; + const notebookCells = notebookEditorWidget.getViewModel().viewCells.filter((cell) => cell.cellKind === CellKind.Markup || includeCodeCells); + const entries: OutlineEntry[] = []; - - for (let i = 0; i < notebookEditorWidget.getLength(); i++) { - const cell = notebookEditorWidget.cellAt(i); - const isMarkdown = cell.cellKind === CellKind.Markup; - if (!isMarkdown && !includeCodeCells) { - continue; - } - - // cap the amount of characters that we look at and use the following logic - // - for MD prefer headings (each header is an entry) - // - otherwise use the first none-empty line of the cell (MD or code) - let content = this._getCellFirstNonEmptyLine(cell); - let hasHeader = false; - - if (isMarkdown) { - const fullContent = cell.getText().substring(0, 10_000); - for (const { depth, text } of getMarkdownHeadersInCell(fullContent)) { - hasHeader = true; - entries.push(new OutlineEntry(entries.length, depth, cell, text, false, false)); - } - - if (!hasHeader) { - // no markdown syntax headers, try to find html tags - const match = fullContent.match(/(.*)<\/h\1>/i); - if (match) { - hasHeader = true; - const level = parseInt(match[1]); - const text = match[2].trim(); - entries.push(new OutlineEntry(entries.length, level, cell, text, false, false)); - } - } - - if (!hasHeader) { - content = renderMarkdownAsPlaintext({ value: content }); - } - } - - if (!hasHeader) { - let preview = content.trim(); - if (preview.length === 0) { - // empty or just whitespace - preview = localize('empty', "empty cell"); - } - - const exeState = !isMarkdown && this._notebookExecutionStateService.getCellExecution(cell.uri); - entries.push(new OutlineEntry(entries.length, 7, cell, preview, !!exeState, exeState ? exeState.isPaused : false)); - } - - if (cell.handle === focused) { - this._activeEntry = entries[entries.length - 1]; - } - + for (const cell of notebookCells) { + entries.push(...this._outlineEntryFactory.getOutlineEntries(cell, entries.length)); // send an event whenever any of the cells change this._entriesDisposables.add(cell.model.onDidChangeContent(() => { this._recomputeState(); @@ -355,6 +229,7 @@ export class NotebookCellOutlineProvider { } })); + this._recomputeActive(); this._onDidChange.fire({}); } @@ -381,18 +256,7 @@ export class NotebookCellOutlineProvider { } } - private _getCellFirstNonEmptyLine(cell: ICellViewModel) { - const textBuffer = cell.textBuffer; - for (let i = 0; i < textBuffer.getLineCount(); i++) { - const firstNonWhitespace = textBuffer.getLineFirstNonWhitespaceColumn(i + 1); - const lineLength = textBuffer.getLineLength(i + 1); - if (firstNonWhitespace < lineLength) { - return textBuffer.getLineContent(i + 1); - } - } - return cell.getText().substring(0, 10_000); - } get isEmpty(): boolean { return this._entries.length === 0; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 2426b24c850..257ead51887 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -152,7 +152,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD private readonly _onDidChangeSelection = this._register(new Emitter()); get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } - private _selectionCollection = new NotebookCellSelectionCollection(); + private _selectionCollection = this._register(new NotebookCellSelectionCollection()); private get selectionHandles() { const handlesSet = new Set(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 426f767e89d..be4036595ac 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -15,8 +15,10 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; + import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; export class ToggleNotebookStickyScroll extends Action2 { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 417114d32e2..3a1d9faaddf 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -294,6 +294,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel }); this._cellListeners.set(mainCells[i].handle, dirtyStateListener); + this._register(mainCells[i]); } this._cells.splice(0, 0, ...mainCells); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 343e77b210a..cdd7b0fc31d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -962,7 +962,8 @@ export const NotebookSetting = { findScope: 'notebook.find.scope', logging: 'notebook.logging', confirmDeleteRunningCell: 'notebook.confirmDeleteRunningCell', - remoteSaving: 'notebook.experimental.remoteSave' + remoteSaving: 'notebook.experimental.remoteSave', + gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols' } as const; export const enum CellStatusbarAlignment { diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index c226a32fe0f..42b5d294c13 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -11,6 +11,7 @@ import { INTERACTIVE_WINDOW_EDITOR_ID, NOTEBOOK_EDITOR_ID } from 'vs/workbench/c //#region Context Keys export const HAS_OPENED_NOTEBOOK = new RawContextKey('userHasOpenedNotebook', false); export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey('notebookFindWidgetFocused', false); +export const InteractiveWindowOpen = new RawContextKey('interactiveWindowOpen', false); // Is Notebook export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', NOTEBOOK_EDITOR_ID); diff --git a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts index 2de6c8fcf3c..357927dbf60 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/cellOperations.test.ts @@ -13,8 +13,11 @@ import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextBuffer, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('CellOperations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('Move cells - single cell', async function () { await withTestNotebook( [ @@ -61,8 +64,8 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { - const foldingModel = new FoldingModel(); + async (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); updateFoldingStateAtIndex(foldingModel, 1, true); @@ -144,8 +147,8 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { - const foldingModel = new FoldingModel(); + async (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); updateFoldingStateAtIndex(foldingModel, 1, true); @@ -536,7 +539,7 @@ suite('CellOperations', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); const insertedCellAbove = insertCell(languageService, editor, 4, CellKind.Code, 'above', 'var a = 0;'); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts index b37d157699b..71a5b40715c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/contributedStatusBarItemController.test.ts @@ -26,7 +26,7 @@ suite('Notebook Statusbar', () => { ['var b = 1;', 'javascript', CellKind.Code, [], {}], ['# header a', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const cellStatusbarSvc = accessor.get(INotebookCellStatusBarService); testDisposables.add(accessor.createInstance(ContributedStatusBarItemController, editor)); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts index b941e70675e..0154bb69eb3 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts @@ -55,7 +55,7 @@ suite('Notebook Find', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); @@ -101,7 +101,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -152,7 +152,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -194,7 +194,7 @@ suite('Notebook Find', () => { ['paragraph 1.3', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { setupEditorForTest(editor, viewModel); accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); @@ -232,7 +232,7 @@ suite('Notebook Find', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(IConfigurationService, configurationService); const state = new FindReplaceState(); const model = new FindModel(editor, state, accessor.get(IConfigurationService)); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts index 43d96a3990d..541dd70ff09 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookClipboard.test.ts @@ -42,7 +42,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); @@ -66,7 +66,7 @@ suite('Notebook Clipboard', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); @@ -97,7 +97,7 @@ suite('Notebook Clipboard', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); @@ -130,7 +130,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } }); const clipboardContrib = new NotebookClipboardContribution(createEditorService(editor)); @@ -148,7 +148,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -182,7 +182,7 @@ suite('Notebook Clipboard', () => { ['paragraph 1', 'markdown', CellKind.Markup, [], {}], ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { let _toCopy: NotebookCellTextModel[] = []; accessor.stub(INotebookService, new class extends mock() { override setToCopy(toCopy: NotebookCellTextModel[]) { _toCopy = toCopy; } @@ -212,7 +212,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -253,7 +253,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { @@ -277,7 +277,7 @@ suite('Notebook Clipboard', () => { ['paragraph 2', 'markdown', CellKind.Markup, [], {}], ['paragraph 3', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { accessor.stub(INotebookService, new class extends mock() { override setToCopy() { } override getToCopy() { diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts new file mode 100644 index 00000000000..79d34e42b58 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CancellationToken } from 'vs/base/common/cancellation'; +import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { ITextModel } from 'vs/editor/common/model'; +import { IOutlineModelService, OutlineModel } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; +import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookOutlineEntryFactory } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory'; +import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; + +suite('Notebook Symbols', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + type textSymbol = { name: string; selectionRange: {}; children?: textSymbol[] }; + const symbolsPerTextModel: Record = {}; + function setSymbolsForTextModel(symbols: textSymbol[], textmodelId = 'textId') { + symbolsPerTextModel[textmodelId] = symbols; + } + + const executionService = new class extends mock() { + override getCellExecution() { return undefined; } + }; + + class OutlineModelStub { + constructor(private textId: string) { } + + getTopLevelSymbols() { + return symbolsPerTextModel[this.textId]; + } + } + const outlineModelService = new class extends mock() { + override getOrCreate(model: ITextModel, arg1: any) { + const outline = new OutlineModelStub(model.id) as unknown as OutlineModel; + return Promise.resolve(outline); + } + override getDebounceValue(arg0: any) { + return 0; + } + }; + + function createCellViewModel(version: number = 1, textmodelId = 'textId') { + return { + textBuffer: { + getLineCount() { return 0; } + }, + getText() { + return '# code'; + }, + model: { + textModel: { + id: textmodelId, + getVersionId() { return version; } + } + } + } as ICellViewModel; + } + + test('Cell without symbols cache', function () { + setSymbolsForTextModel([{ name: 'var', selectionRange: {} }]); + const entryFactory = new NotebookOutlineEntryFactory(executionService); + const entries = entryFactory.getOutlineEntries(createCellViewModel(), 0); + + assert.equal(entries.length, 1, 'no entries created'); + assert.equal(entries[0].label, '# code', 'entry should fall back to first line of cell'); + }); + + test('Cell with simple symbols', async function () { + setSymbolsForTextModel([{ name: 'var1', selectionRange: {} }, { name: 'var2', selectionRange: {} }]); + const entryFactory = new NotebookOutlineEntryFactory(executionService); + const cell = createCellViewModel(); + + await entryFactory.cacheSymbols(cell.model.textModel!, outlineModelService, CancellationToken.None); + const entries = entryFactory.getOutlineEntries(cell, 0); + + assert.equal(entries.length, 2, 'wrong number of outline entries'); + assert.equal(entries[0].label, 'var1'); + // 6 levels for markdown, all code symbols are greater than the max markdown level + assert.equal(entries[0].level, 7); + assert.equal(entries[0].index, 0); + assert.equal(entries[1].label, 'var2'); + assert.equal(entries[1].level, 7); + assert.equal(entries[1].index, 1); + }); + + test('Cell with nested symbols', async function () { + setSymbolsForTextModel([ + { name: 'root1', selectionRange: {}, children: [{ name: 'nested1', selectionRange: {} }, { name: 'nested2', selectionRange: {} }] }, + { name: 'root2', selectionRange: {}, children: [{ name: 'nested1', selectionRange: {} }] } + ]); + const entryFactory = new NotebookOutlineEntryFactory(executionService); + const cell = createCellViewModel(); + + await entryFactory.cacheSymbols(cell.model.textModel!, outlineModelService, CancellationToken.None); + const entries = entryFactory.getOutlineEntries(createCellViewModel(), 0); + + assert.equal(entries.length, 5, 'wrong number of outline entries'); + assert.equal(entries[0].label, 'root1'); + assert.equal(entries[0].level, 7); + assert.equal(entries[1].label, 'nested1'); + assert.equal(entries[1].level, 8); + assert.equal(entries[2].label, 'nested2'); + assert.equal(entries[2].level, 8); + assert.equal(entries[3].label, 'root2'); + assert.equal(entries[3].level, 7); + assert.equal(entries[4].label, 'nested1'); + assert.equal(entries[4].level, 8); + }); + + test('Multiple Cells with symbols', async function () { + setSymbolsForTextModel([{ name: 'var1', selectionRange: {} }], '$1'); + setSymbolsForTextModel([{ name: 'var2', selectionRange: {} }], '$2'); + const entryFactory = new NotebookOutlineEntryFactory(executionService); + + const cell1 = createCellViewModel(1, '$1'); + const cell2 = createCellViewModel(1, '$2'); + await entryFactory.cacheSymbols(cell1.model.textModel!, outlineModelService, CancellationToken.None); + await entryFactory.cacheSymbols(cell2.model.textModel!, outlineModelService, CancellationToken.None); + + const entries1 = entryFactory.getOutlineEntries(createCellViewModel(1, '$1'), 0); + const entries2 = entryFactory.getOutlineEntries(createCellViewModel(1, '$2'), 0); + + + assert.equal(entries1.length, 1, 'wrong number of outline entries'); + assert.equal(entries1[0].label, 'var1'); + assert.equal(entries2.length, 1, 'wrong number of outline entries'); + assert.equal(entries2[0].label, 'var2'); + }); + +}); diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts index b40b1e63633..3f1a6f021fa 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookUndoRedo.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -15,7 +16,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); assert.strictEqual(viewModel.length, 2); assert.strictEqual(viewModel.getVersionId(), 0); @@ -59,7 +60,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] @@ -100,7 +101,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 2, cells: [] @@ -132,9 +133,9 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); - const cellList = createNotebookCellList(accessor); + const cellList = createNotebookCellList(accessor, new DisposableStore()); cellList.attachViewModel(viewModel); cellList.setFocus([1]); @@ -173,7 +174,7 @@ suite('Notebook Undo/Redo', () => { ['# header 1', 'markdown', CellKind.Markup, [], {}], ['body', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel, accessor) => { + async (editor, viewModel, _ds, accessor) => { const languageService = accessor.get(ILanguageService); editor.textModel.applyEdits([{ diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index 0d8e843411f..fb3db12dace 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -5,20 +5,25 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCellList', () => { - let disposables: DisposableStore; + let testDisposables: DisposableStore; let instantiationService: TestInstantiationService; - suiteSetup(() => { - disposables = new DisposableStore(); - instantiationService = setupInstantiationService(disposables); + teardown(() => { + testDisposables.dispose(); }); - suiteTeardown(() => disposables.dispose()); + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { + testDisposables = new DisposableStore(); + instantiationService = setupInstantiationService(testDisposables); + }); test('revealElementsInView: reveal fully visible cell should not scroll', async function () { await withTestNotebook( @@ -29,7 +34,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], cellLineNumberStates: {}, @@ -39,7 +44,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -76,7 +81,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -86,7 +91,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -120,7 +125,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -130,7 +135,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); // without paddingBottom, the last 20 px will always be hidden due to `topInsertToolbarHeight` cellList.updateOptions({ paddingBottom: 100 }); cellList.attachViewModel(viewModel); @@ -157,7 +162,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -167,7 +172,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -199,7 +204,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -209,7 +214,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -252,7 +257,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -262,7 +267,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -288,7 +293,7 @@ suite('NotebookCellList', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], @@ -298,7 +303,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -321,8 +326,8 @@ suite('NotebookCellList', () => { await withTestNotebook( [ ], - async (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService); + async (editor, viewModel, disposables) => { + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell @@ -337,7 +342,7 @@ suite('NotebookCellList', () => { [ ['# header a', 'markdown', CellKind.Markup, [], {}], ], - async (editor, viewModel) => { + async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false], editorViewStates: [null], @@ -347,7 +352,7 @@ suite('NotebookCellList', () => { collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts index 1fa57c44f31..83d0164b035 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCommon.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { CellKind, CellUri, diff, MimeTypeDisplayOrder, NotebookWorkingCopyTypeIdentifier } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -14,18 +15,18 @@ import { cellIndexesToRanges, cellRangesToIndexes, reduceCellRanges } from 'vs/w import { setupInstantiationService, TestCell } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCommon', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let languageService: ILanguageService; - suiteSetup(() => { + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); languageService = instantiationService.get(ILanguageService); }); - suiteTeardown(() => disposables.dispose()); - test('sortMimeTypes default orders', function () { assert.deepStrictEqual(new MimeTypeDisplayOrder().sort( [ @@ -99,6 +100,8 @@ suite('NotebookCommon', () => { Mimes.text ] ); + + disposables.dispose(); }); @@ -163,6 +166,8 @@ suite('NotebookCommon', () => { Mimes.text ] ); + + disposables.dispose(); }); test('prioritizes mimetypes', () => { @@ -197,6 +202,8 @@ suite('NotebookCommon', () => { const m2 = new MimeTypeDisplayOrder(['a', 'b']); m2.prioritize('b', ['a', 'b', 'a', 'q']); assert.deepStrictEqual(m2.toArray(), ['b', 'a']); + + disposables.dispose(); }); test('sortMimeTypes glob', function () { @@ -224,6 +231,8 @@ suite('NotebookCommon', () => { ], 'glob *' ); + + disposables.dispose(); }); test('diff cells', function () { @@ -231,7 +240,7 @@ suite('NotebookCommon', () => { for (let i = 0; i < 5; i++) { cells.push( - new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], languageService) + disposables.add(new TestCell('notebook', i, `var a = ${i};`, 'javascript', CellKind.Code, [], languageService)) ); } @@ -257,8 +266,8 @@ suite('NotebookCommon', () => { ] ); - const cellA = new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], languageService); - const cellB = new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], languageService); + const cellA = disposables.add(new TestCell('notebook', 6, 'var a = 6;', 'javascript', CellKind.Code, [], languageService)); + const cellB = disposables.add(new TestCell('notebook', 7, 'var a = 7;', 'javascript', CellKind.Code, [], languageService)); const modifiedCells = [ cells[0], @@ -287,7 +296,10 @@ suite('NotebookCommon', () => { } ] ); + + disposables.dispose(); }); + }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts index f92b02ba51e..6136451c28a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookDiff.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { VSBuffer } from 'vs/base/common/buffer'; import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; import { Mimes } from 'vs/base/common/mime'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { NotebookDiffEditorEventDispatcher } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor'; @@ -28,16 +29,16 @@ class CellSequence implements ISequence { } } - suite('NotebookCommon', () => { const configurationService = new TestConfigurationService(); + ensureNoDisposablesAreLeakedInTestSuite(); test('diff different source', async () => { await withTestNotebookDiffModel([ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ], [ ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); @@ -53,12 +54,19 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); assert.strictEqual(diffViewModels.viewModels.length, 1); assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -69,7 +77,7 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ['', 'javascript', CellKind.Code, [], {}] - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); @@ -85,13 +93,21 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); assert.strictEqual(diffViewModels.viewModels.length, 2); assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); assert.strictEqual(diffViewModels.viewModels[1].type, 'unchanged'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -100,7 +116,7 @@ suite('NotebookCommon', () => { ['123456789', 'javascript', CellKind.Code, [], {}] ], [ ['987654321', 'javascript', CellKind.Code, [], {}], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); @@ -116,12 +132,20 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); assert.strictEqual(diffViewModels.viewModels.length, 1); assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -138,7 +162,7 @@ suite('NotebookCommon', () => { ' \'This version is debugged.\'\n', ' return a * b' ].join(''), 'javascript', CellKind.Code, [], {}], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); assert.strictEqual(diffResult.changes.length, 1); @@ -154,12 +178,20 @@ suite('NotebookCommon', () => { modifiedLength: 1 }]); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); assert.strictEqual(diffViewModels.viewModels.length, 1); assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -172,10 +204,10 @@ suite('NotebookCommon', () => { [['def foo(x, y):\n', ' return x * y\n', 'foo(1, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([6])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 5 }], [['def foe(x, y):\n', ' return x + y\n', 'foe(3, 2)'].join(''), 'javascript', CellKind.Code, [{ outputId: 'someId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([2])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 6 }], ['', 'javascript', CellKind.Code, [], {}] - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); @@ -183,6 +215,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); assert.strictEqual(diffViewModels.viewModels[1].type, 'modified'); assert.strictEqual(diffViewModels.viewModels[2].type, 'unchanged'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -195,10 +235,10 @@ suite('NotebookCommon', () => { ['This is a test notebook with markdown cells only', 'markdown', CellKind.Markup, [], {}], ['Lorem ipsum dolor sit amet', 'markdown', CellKind.Markup, [], {}], ['In the news', 'markdown', CellKind.Markup, [], {}], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); @@ -206,6 +246,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffViewModels.viewModels[0].type, 'modified'); assert.strictEqual(diffViewModels.viewModels[1].type, 'unchanged'); assert.strictEqual(diffViewModels.viewModels[2].type, 'modified'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -217,8 +265,8 @@ suite('NotebookCommon', () => { ['var h = 8;', 'javascript', CellKind.Code, [], {}], ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] - ], (model, accessor) => { - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + ], (model, disposables, accessor) => { + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: { changes: [{ @@ -235,6 +283,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffResult.viewModels[0].type, 'insert'); assert.strictEqual(diffResult.viewModels[1].type, 'unchanged'); assert.strictEqual(diffResult.viewModels[2].type, 'unchanged'); + + diffResult.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -257,8 +313,8 @@ suite('NotebookCommon', () => { ['var e = 5;', 'javascript', CellKind.Code, [], {}], ['var f = 6;', 'javascript', CellKind.Code, [], {}], ['var g = 7;', 'javascript', CellKind.Code, [], {}], - ], async (model, accessor) => { - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + ], async (model, disposables, accessor) => { + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: { changes: [{ @@ -285,6 +341,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffResult.viewModels[5].type, 'unchanged'); assert.strictEqual(diffResult.viewModels[6].type, 'unchanged'); assert.strictEqual(diffResult.viewModels[7].type, 'unchanged'); + + diffResult.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -307,8 +371,8 @@ suite('NotebookCommon', () => { ['var e = 5;', 'javascript', CellKind.Code, [], {}], ['var f = 6;', 'javascript', CellKind.Code, [], {}], ['var g = 7;', 'javascript', CellKind.Code, [], {}], - ], async (model, accessor) => { - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + ], async (model, disposables, accessor) => { + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffResult = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: { changes: [{ @@ -330,6 +394,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffResult.viewModels[5].type, 'unchanged'); assert.strictEqual(diffResult.viewModels[6].type, 'unchanged'); assert.strictEqual(diffResult.viewModels[7].type, 'unchanged'); + + diffResult.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -461,10 +533,10 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); @@ -472,6 +544,14 @@ suite('NotebookCommon', () => { assert.strictEqual(diffViewModels.viewModels[0].type, 'unchanged'); assert.strictEqual(diffViewModels.viewModels[0].checkIfOutputsModified(), false); assert.strictEqual(diffViewModels.viewModels[1].type, 'modified'); + + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); @@ -482,16 +562,23 @@ suite('NotebookCommon', () => { ], [ ['x', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([3])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], ['y', 'javascript', CellKind.Code, [{ outputId: 'someOtherId', outputs: [{ mime: Mimes.text, data: VSBuffer.wrap(new Uint8Array([5])) }] }], { custom: { metadata: { collapsed: false } }, executionOrder: 3 }], - ], (model, accessor) => { + ], (model, disposables, accessor) => { const diff = new LcsDiff(new CellSequence(model.original.notebook), new CellSequence(model.modified.notebook)); const diffResult = diff.ComputeDiff(false); - const eventDispatcher = new NotebookDiffEditorEventDispatcher(); + const eventDispatcher = disposables.add(new NotebookDiffEditorEventDispatcher()); const diffViewModels = NotebookTextDiffEditor.computeDiff(accessor, configurationService, model, eventDispatcher, { cellsDiff: diffResult }, undefined); assert.strictEqual(diffViewModels.viewModels.length, 2); assert.strictEqual(diffViewModels.viewModels[0].original!.textModel.equal(diffViewModels.viewModels[0].modified!.textModel), true); assert.strictEqual(diffViewModels.viewModels[1].original!.textModel.equal(diffViewModels.viewModels[1].modified!.textModel), false); + diffViewModels.viewModels.forEach(vm => { + vm.original?.dispose(); + vm.modified?.dispose(); + vm.dispose(); + }); + model.original.notebook.dispose(); + model.modified.notebook.dispose(); }); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts index c1c39b0b693..1bd5ca8f03c 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditor.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; @@ -12,18 +11,24 @@ import { expandCellRangesWithHiddenCells, INotebookEditor } from 'vs/workbench/c import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { DisposableStore } from 'vs/base/common/lifecycle'; suite('ListViewInfoAccessor', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - suiteSetup(() => { + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); }); - suiteTeardown(() => disposables.dispose()); - test('basics', async function () { await withTestNotebook( [ @@ -33,13 +38,13 @@ suite('ListViewInfoAccessor', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['var c = 3;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); - const listViewInfoAccessor = new ListViewInfoAccessor(cellList); + const listViewInfoAccessor = ds.add(new ListViewInfoAccessor(cellList)); assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.cellAt(0)!), 0); assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.cellAt(1)!), 1); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 13c2d908756..8874152bdb9 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -241,7 +241,7 @@ suite('NotebookExecutionStateService', () => { }); }); - test('getExecution and onDidChangeExecution', async function () { + test('getExecution and onDidChangeExecution 2', async function () { return withTestNotebook([], async viewModel => { testNotebookModel = viewModel.notebookDocument; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts index 11555890b55..4acfd482bf8 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookFolding.test.ts @@ -10,18 +10,22 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Notebook Folding', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - suiteSetup(() => { + teardown(() => disposables.dispose()); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); instantiationService.spy(IUndoRedoService, 'pushElement'); }); - suiteTeardown(() => disposables.dispose()); test('Folding based on markdown cells', async function () { await withTestNotebook( @@ -34,8 +38,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -61,8 +65,8 @@ suite('Notebook Folding', () => { ['## header 2.1', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'python', CellKind.Code, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -88,8 +92,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingController = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingController = ds.add(new FoldingModel()); foldingController.attachViewModel(viewModel); assert.strictEqual(foldingController.regions.findRange(1), 0); @@ -119,8 +123,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -140,8 +144,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -162,8 +166,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -186,8 +190,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); viewModel.updateFoldingRanges(foldingModel.regions); @@ -245,8 +249,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); viewModel.updateFoldingRanges(foldingModel.regions); @@ -273,8 +277,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, @@ -305,8 +309,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, @@ -339,8 +343,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); viewModel.updateFoldingRanges(foldingModel.regions); @@ -375,8 +379,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markup, [], {}], ['var e = 7;', 'markdown', CellKind.Markup, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ { start: 5, end: 6 }, diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts index 52eadf224ae..24d459f9eef 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelHistory.test.ts @@ -20,18 +20,24 @@ import { IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { NotebookKernelHistoryService } from 'vs/workbench/contrib/notebook/browser/services/notebookKernelHistoryServiceImpl'; import { IApplicationStorageValueChangeEvent, IProfileStorageValueChangeEvent, IStorageService, IStorageValueChangeEvent, IWillSaveStateEvent, IWorkspaceStorageValueChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('NotebookKernelHistoryService', () => { + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let kernelService: INotebookKernelService; - let disposables: DisposableStore; let onDidAddNotebookDocument: Emitter; + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + setup(function () { disposables = new DisposableStore(); - onDidAddNotebookDocument = new Emitter(); disposables.add(onDidAddNotebookDocument); @@ -50,14 +56,10 @@ suite('NotebookKernelHistoryService', () => { }; } }); - kernelService = instantiationService.createInstance(NotebookKernelService); + kernelService = disposables.add(instantiationService.createInstance(NotebookKernelService)); instantiationService.set(INotebookKernelService, kernelService); }); - teardown(() => { - disposables.dispose(); - }); - test('notebook kernel empty history', function () { const u1 = URI.parse('foo:///one'); @@ -65,8 +67,8 @@ suite('NotebookKernelHistoryService', () => { const k1 = new TestNotebookKernel({ label: 'z', viewType: 'foo' }); const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); - kernelService.registerKernel(k1); - kernelService.registerKernel(k2); + disposables.add(kernelService.registerKernel(k1)); + disposables.add(kernelService.registerKernel(k2)); instantiationService.stub(IStorageService, new class extends mock() { override onWillSaveState: Event = Event.None; @@ -96,7 +98,7 @@ suite('NotebookKernelHistoryService', () => { override debug() { } }); - const kernelHistoryService = instantiationService.createInstance(NotebookKernelHistoryService); + const kernelHistoryService = disposables.add(instantiationService.createInstance(NotebookKernelHistoryService)); let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); assert.equal(info.all.length, 0); @@ -119,9 +121,9 @@ suite('NotebookKernelHistoryService', () => { const k2 = new TestNotebookKernel({ label: 'a', viewType: 'foo' }); const k3 = new TestNotebookKernel({ label: 'b', viewType: 'foo' }); - kernelService.registerKernel(k1); - kernelService.registerKernel(k2); - kernelService.registerKernel(k3); + disposables.add(kernelService.registerKernel(k1)); + disposables.add(kernelService.registerKernel(k2)); + disposables.add(kernelService.registerKernel(k3)); instantiationService.stub(IStorageService, new class extends mock() { override onWillSaveState: Event = Event.None; @@ -153,7 +155,7 @@ suite('NotebookKernelHistoryService', () => { override debug() { } }); - const kernelHistoryService = instantiationService.createInstance(NotebookKernelHistoryService); + const kernelHistoryService = disposables.add(instantiationService.createInstance(NotebookKernelHistoryService)); let info = kernelHistoryService.getKernels({ uri: u1, viewType: 'foo' }); assert.equal(info.all.length, 1); assert.deepStrictEqual(info.selected, undefined); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts index d661240cf3c..b1aa61118fd 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookSelection.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; @@ -12,6 +11,8 @@ import { runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controlle import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection'; import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { DisposableStore } from 'vs/base/common/lifecycle'; suite('NotebookSelection', () => { test('focus is never empty', function () { @@ -24,18 +25,22 @@ suite('NotebookSelection', () => { }); suite('NotebookCellList focus/selection', () => { - let disposables: DisposableStore; let instantiationService: TestInstantiationService; let languageService: ILanguageService; - suiteSetup(() => { + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); languageService = instantiationService.get(ILanguageService); }); - suiteTeardown(() => disposables.dispose()); test('notebook cell list setFocus', async function () { await withTestNotebook( @@ -43,8 +48,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -63,8 +68,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -84,8 +89,8 @@ suite('NotebookCellList focus/selection', () => { ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); @@ -111,8 +116,8 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const cellList = createNotebookCellList(instantiationService); + (editor, viewModel, ds) => { + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); @@ -146,11 +151,11 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 5); assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); @@ -192,11 +197,11 @@ suite('NotebookCellList focus/selection', () => { ['# header d', 'markdown', CellKind.Markup, [], {}], ['var e = 4;', 'javascript', CellKind.Code, [], {}], ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); cellList.setFocus([0]); cellList.setSelection([0]); @@ -220,8 +225,8 @@ suite('NotebookCellList focus/selection', () => { // mimic undo editor.textModel.applyEdits([{ editType: CellEditType.Replace, index: 0, count: 0, cells: [ - new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService), - new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService) + ds.add(new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], languageService)), + ds.add(new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], languageService)) ] }], true, undefined, () => undefined, undefined, false); viewModel.updateFoldingRanges(foldingModel.regions); @@ -229,8 +234,6 @@ suite('NotebookCellList focus/selection', () => { assert.strictEqual(cellList.getModelIndex2(0), 0); assert.strictEqual(cellList.getModelIndex2(1), 1); assert.strictEqual(cellList.getModelIndex2(2), 2); - - }); }); @@ -243,11 +246,11 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], - (editor, viewModel) => { - const foldingModel = new FoldingModel(); + (editor, viewModel, ds) => { + const foldingModel = ds.add(new FoldingModel()); foldingModel.attachViewModel(viewModel); - const cellList = createNotebookCellList(instantiationService); + const cellList = createNotebookCellList(instantiationService, ds); cellList.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts index 99cb8117b61..ead402ce540 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookStickyScroll.test.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isWeb } from 'vs/base/common/platform'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; import { mock } from 'vs/base/test/common/mock'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NotebookCellOutline } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import { INotebookEditor, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; -import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; import { NotebookStickyLine, computeContent } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -21,19 +22,22 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; (isWeb ? suite.skip : suite)('NotebookEditorStickyScroll', () => { - let disposables: DisposableStore; let instantiationService: TestInstantiationService; const domNode: HTMLElement = document.createElement('div'); - suiteSetup(() => { + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + + setup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); }); - suiteTeardown(() => disposables.dispose()); - function getOutline(editor: any) { if (!editor.hasModel()) { assert.ok(false, 'MUST have active text editor'); @@ -47,8 +51,11 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; return outline; } - function nbStickyTestHelper(domNode: HTMLElement, notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[]) { + function nbStickyTestHelper(domNode: HTMLElement, notebookEditor: INotebookEditor, notebookCellList: INotebookCellList, notebookOutlineEntries: OutlineEntry[], disposables: Pick) { const output = computeContent(domNode, notebookEditor, notebookCellList, notebookOutlineEntries); + for (const stickyLine of output.values()) { + disposables.add(stickyLine.line); + } return createStickyTestElement(output.values()); } @@ -84,16 +91,18 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = disposables.add(createNotebookCellList(instantiationService, disposables)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(0); editor.visibleRanges = [{ start: 0, end: 8 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, disposables); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -109,7 +118,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 300 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 350 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 8 }, () => false), editorViewStates: Array.from({ length: 8 }, () => null), @@ -119,17 +128,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(175); editor.visibleRanges = [{ start: 3, end: 8 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -146,7 +157,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 350 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 400 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 9 }, () => false), editorViewStates: Array.from({ length: 9 }, () => null), @@ -156,17 +167,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(325); // room for a single header editor.visibleRanges = [{ start: 6, end: 9 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -184,7 +197,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], // 400 ['var c = 2;', 'javascript', CellKind.Code, [], {}] // 450 ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -194,17 +207,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(175); // room for a single header editor.visibleRanges = [{ start: 3, end: 10 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -221,7 +236,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 8 }, () => false), editorViewStates: Array.from({ length: 8 }, () => null), @@ -231,17 +246,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(50); editor.visibleRanges = [{ start: 0, end: 8 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -260,7 +277,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['# header b', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -270,17 +287,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(125); editor.visibleRanges = [{ start: 2, end: 10 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -299,7 +318,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['### header bbb', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 10 }, () => false), editorViewStates: Array.from({ length: 10 }, () => null), @@ -309,17 +328,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(375); editor.visibleRanges = [{ start: 7, end: 10 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); @@ -340,7 +361,7 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; ['### header bbb', 'markdown', CellKind.Markup, [], {}], ['var c = 2;', 'javascript', CellKind.Code, [], {}] ], - async (editor, viewModel) => { + async (editor, viewModel, ds) => { viewModel.restoreEditorViewState({ editingCells: Array.from({ length: 12 }, () => false), editorViewStates: Array.from({ length: 12 }, () => null), @@ -350,17 +371,19 @@ import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; collapsedOutputCells: {}, }); - const cellList = createNotebookCellList(instantiationService); + const cellList = ds.add(createNotebookCellList(instantiationService, ds)); cellList.attachViewModel(viewModel); cellList.layout(400, 100); editor.setScrollTop(350); editor.visibleRanges = [{ start: 7, end: 12 }]; - const notebookOutlineEntries = getOutline(editor).entries; - const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries); + const outline = getOutline(editor); + const notebookOutlineEntries = outline.entries; + const resultingMap = nbStickyTestHelper(domNode, editor, cellList, notebookOutlineEntries, ds); await assertSnapshot(resultingMap); + outline.dispose(); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts index 184e3ea1951..46e50d6ed4e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookTextModel.test.ts @@ -39,8 +39,8 @@ suite('NotebookTextModel', () => { (editor) => { const textModel = editor.textModel; textModel.applyEdits([ - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, - { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [disposables.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] }, + { editType: CellEditType.Replace, index: 3, count: 0, cells: [disposables.add(new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))] }, ], true, undefined, () => undefined, undefined, true); assert.strictEqual(textModel.cells.length, 6); @@ -62,8 +62,8 @@ suite('NotebookTextModel', () => { (editor) => { const textModel = editor.textModel; textModel.applyEdits([ - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService)] }, - { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService)] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [disposables.add(new TestCell(textModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], languageService))] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [disposables.add(new TestCell(textModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], languageService))] }, ], true, undefined, () => undefined, undefined, true); assert.strictEqual(textModel.cells.length, 6); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index b5134c88bdf..13dc30523c7 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -28,8 +28,11 @@ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { NotebookEditorTestModel, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { IBaseCellEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('NotebookViewModel', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + let disposables: DisposableStore; let instantiationService: TestInstantiationService; let textModelService: ITextModelService; @@ -58,9 +61,16 @@ suite('NotebookViewModel', () => { test('ctor', function () { const notebook = new NotebookTextModel('notebook', URI.parse('test'), [], {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, transientOutputs: false, cellContentMetadata: {} }, undoRedoService, modelService, languageService); const model = new NotebookEditorTestModel(notebook); - const viewContext = new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)); + const options = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); + const eventDispatcher = new NotebookEventDispatcher(); + const viewContext = new ViewContext(options, eventDispatcher, () => ({} as IBaseCellEditorOptions)); const viewModel = new NotebookViewModel('notebook', model.notebook, viewContext, null, { isReadOnly: false }, instantiationService, bulkEditService, undoRedoService, textModelService, notebookExecutionStateService); assert.strictEqual(viewModel.viewType, 'notebook'); + notebook.dispose(); + model.dispose(); + options.dispose(); + eventDispatcher.dispose(); + viewModel.dispose(); }); test('insert/delete', async function () { @@ -79,6 +89,9 @@ suite('NotebookViewModel', () => { assert.strictEqual(viewModel.length, 2); assert.strictEqual(viewModel.notebookDocument.cells.length, 2); assert.strictEqual(viewModel.getCellIndex(cell), -1); + + cell.dispose(); + cell.model.dispose(); } ); }); @@ -105,6 +118,11 @@ suite('NotebookViewModel', () => { assert.strictEqual(viewModel.length, 3); assert.strictEqual(viewModel.notebookDocument.cells.length, 3); assert.strictEqual(viewModel.getCellIndex(cell2), 2); + + cell.dispose(); + cell.model.dispose(); + cell2.dispose(); + cell2.model.dispose(); } ); }); @@ -136,6 +154,8 @@ function getVisibleCells(cells: T[], hiddenRanges: ICellRange[]) { } suite('NotebookViewModel Decorations', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('tracking range', async function () { await withTestNotebook( [ @@ -153,7 +173,7 @@ suite('NotebookViewModel Decorations', () => { end: 2, }); - insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true, true); + const cell1 = insertCellAtIndex(viewModel, 0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true, true); assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), { start: 2, @@ -167,7 +187,7 @@ suite('NotebookViewModel Decorations', () => { end: 2 }); - insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true, true); + const cell2 = insertCellAtIndex(viewModel, 3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true, true); assert.deepStrictEqual(viewModel.getTrackedRange(trackedId!), { start: 1, @@ -187,6 +207,11 @@ suite('NotebookViewModel Decorations', () => { end: 1 }); + + cell1.dispose(); + cell1.model.dispose(); + cell2.dispose(); + cell2.model.dispose(); } ); }); @@ -268,13 +293,11 @@ suite('NotebookViewModel Decorations', () => { return original.indexOf(a) >= 0; }), [{ start: 1, deleteCount: 1, toInsert: [2, 6] }]); }); - - test('hidden ranges', async function () { - - }); }); suite('NotebookViewModel API', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + test('#115432, get nearest code cell', async function () { await withTestNotebook( [ diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts index f36258029d7..34c0a8c1a01 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookWorkbenchToolbar.test.ts @@ -6,6 +6,7 @@ import { workbenchCalculateActions, workbenchDynamicCalculateActions } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import * as assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface IActionModel { action: IAction; @@ -25,6 +26,7 @@ interface IActionModel { * ex: action with size 50 requires 58px of space */ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const defaultSecondaryActionModels: IActionModel[] = [ { action: new Action('secondaryAction0', 'Secondary Action 0'), size: 50, visible: true, renderLabel: true }, @@ -34,6 +36,9 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { const defaultSecondaryActions: IAction[] = defaultSecondaryActionModels.map(action => action.action); const separator: IActionModel = { action: new Separator(), size: 1, visible: true, renderLabel: true }; + setup(function () { + defaultSecondaryActionModels.forEach(action => disposables.add(action.action)); + }); test('should return empty primary and secondary actions when given empty initial actions', () => { const result = workbenchCalculateActions([], [], 100); @@ -43,9 +48,9 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should return all primary actions when they fit within the container width', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action2', 'Action 2')), size: 50, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 200); assert.deepEqual(result.primaryActions, actions); @@ -54,9 +59,9 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should move actions to secondary when they do not fit within the container width', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action2', 'Action 2')), size: 50, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 100); assert.deepEqual(result.primaryActions, [actions[0]]); @@ -65,10 +70,10 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should ignore second separator when two separators are in a row', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 125); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]]); @@ -77,9 +82,9 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should ignore separators when they are at the end of the resulting primary actions', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 200); @@ -89,10 +94,10 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should keep actions with size 0 in primary actions', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true }, - { action: new Action('action3', 'Action 3'), size: 0, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action2', 'Action 2')), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action3', 'Action 3')), size: 0, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 116); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]]); @@ -101,9 +106,9 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should not render separator if preceeded by size 0 action(s).', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 0, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 0, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 50, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 116); assert.deepEqual(result.primaryActions, [actions[0], actions[2]]); @@ -112,12 +117,12 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { test('should not render second separator if space between is hidden (size 0) actions.', () => { const actions: IActionModel[] = [ - { action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action0', 'Action 0')), size: 50, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, - { action: new Action('action1', 'Action 1'), size: 0, visible: true, renderLabel: true }, - { action: new Action('action2', 'Action 2'), size: 0, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action1', 'Action 1')), size: 0, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action2', 'Action 2')), size: 0, visible: true, renderLabel: true }, { action: new Separator(), size: 1, visible: true, renderLabel: true }, - { action: new Action('action3', 'Action 3'), size: 50, visible: true, renderLabel: true }, + { action: disposables.add(new Action('action3', 'Action 3')), size: 50, visible: true, renderLabel: true }, ]; const result = workbenchCalculateActions(actions, defaultSecondaryActions, 300); assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[2], actions[3], actions[5]]); @@ -126,6 +131,7 @@ suite('Workbench Toolbar calculateActions (strategy always + never)', () => { }); suite('Workbench Toolbar Dynamic calculateActions (strategy dynamic)', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const actionTemplate = [ new Action('action0', 'Action 0'), @@ -141,6 +147,9 @@ suite('Workbench Toolbar Dynamic calculateActions (strategy dynamic)', () => { ]; const defaultSecondaryActions: IAction[] = defaultSecondaryActionModels.map(action => action.action); + setup(function () { + defaultSecondaryActionModels.forEach(action => disposables.add(action.action)); + }); test('should return empty primary and secondary actions when given empty initial actions', () => { const result = workbenchDynamicCalculateActions([], [], 100); diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index fb7f0a56f29..ab178faad20 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -173,33 +173,33 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi } } -export function setupInstantiationService(disposables = new DisposableStore()) { +export function setupInstantiationService(disposables: DisposableStore) { const instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(ILanguageService, disposables.add(new LanguageService())); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); - instantiationService.stub(ILanguageConfigurationService, new TestLanguageConfigurationService()); - instantiationService.stub(IModelService, instantiationService.createInstance(ModelService)); - instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); - instantiationService.stub(IListService, instantiationService.createInstance(ListService)); + instantiationService.stub(ILanguageConfigurationService, disposables.add(new TestLanguageConfigurationService())); + instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelService))); + instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); + instantiationService.stub(IContextKeyService, disposables.add(instantiationService.createInstance(ContextKeyService))); + instantiationService.stub(IListService, disposables.add(instantiationService.createInstance(ListService))); instantiationService.stub(ILayoutService, new TestLayoutService()); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IClipboardService, TestClipboardService); - instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(IStorageService, disposables.add(new TestStorageService())); instantiationService.stub(IWorkspaceTrustRequestService, disposables.add(new TestWorkspaceTrustRequestService(true))); instantiationService.stub(INotebookExecutionStateService, new TestNotebookExecutionStateService()); instantiationService.stub(IKeybindingService, new MockKeybindingService()); - instantiationService.stub(INotebookCellStatusBarService, new NotebookCellStatusBarService()); + instantiationService.stub(INotebookCellStatusBarService, disposables.add(new NotebookCellStatusBarService())); return instantiationService; } -function _createTestNotebookEditor(instantiationService: TestInstantiationService, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: IActiveNotebookEditorDelegate; viewModel: NotebookViewModel } { +function _createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: DisposableStore, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: IActiveNotebookEditorDelegate; viewModel: NotebookViewModel } { const viewType = 'notebook'; - const notebook = instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map((cell): ICellDto2 => { + const notebook = disposables.add(instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map((cell): ICellDto2 => { return { source: cell[0], mime: undefined, @@ -208,16 +208,16 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic outputs: cell[3] ?? [], metadata: cell[4] }; - }), {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, cellContentMetadata: {}, transientOutputs: false }); + }), {}, { transientCellMetadata: {}, transientDocumentMetadata: {}, cellContentMetadata: {}, transientOutputs: false })); - const model = new NotebookEditorTestModel(notebook); - const notebookOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); - const viewContext = new ViewContext(notebookOptions, new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)); - const viewModel: NotebookViewModel = instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false }); + const model = disposables.add(new NotebookEditorTestModel(notebook)); + const notebookOptions = disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)); + const viewContext = new ViewContext(notebookOptions, disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)); + const viewModel: NotebookViewModel = disposables.add(instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, viewContext, null, { isReadOnly: false })); - const cellList = createNotebookCellList(instantiationService, viewContext); + const cellList = disposables.add(createNotebookCellList(instantiationService, disposables, viewContext)); cellList.attachViewModel(viewModel); - const listViewInfoAccessor = new ListViewInfoAccessor(cellList); + const listViewInfoAccessor = disposables.add(new ListViewInfoAccessor(cellList)); let visibleRanges: ICellRange[] = [{ start: 0, end: 100 }]; @@ -339,15 +339,15 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic return { editor: notebookEditor, viewModel }; } -export function createTestNotebookEditor(instantiationService: TestInstantiationService, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: INotebookEditorDelegate; viewModel: NotebookViewModel } { - return _createTestNotebookEditor(instantiationService, cells); +export function createTestNotebookEditor(instantiationService: TestInstantiationService, disposables: DisposableStore, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][]): { editor: INotebookEditorDelegate; viewModel: NotebookViewModel } { + return _createTestNotebookEditor(instantiationService, disposables, cells); } -export async function withTestNotebookDiffModel(originalCells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], modifiedCells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (diffModel: INotebookDiffEditorModel, accessor: TestInstantiationService) => Promise | R): Promise { +export async function withTestNotebookDiffModel(originalCells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], modifiedCells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (diffModel: INotebookDiffEditorModel, disposables: DisposableStore, accessor: TestInstantiationService) => Promise | R): Promise { const disposables = new DisposableStore(); const instantiationService = setupInstantiationService(disposables); - const originalNotebook = createTestNotebookEditor(instantiationService, originalCells); - const modifiedNotebook = createTestNotebookEditor(instantiationService, modifiedCells); + const originalNotebook = createTestNotebookEditor(instantiationService, disposables, originalCells); + const modifiedNotebook = createTestNotebookEditor(instantiationService, disposables, modifiedCells); const originalResource = new class extends mock() { override get notebook() { return originalNotebook.viewModel.notebookDocument; @@ -369,7 +369,7 @@ export async function withTestNotebookDiffModel(originalCells: [source: } }; - const res = await callback(model, instantiationService); + const res = await callback(model, disposables, instantiationService); if (res instanceof Promise) { res.finally(() => { originalNotebook.editor.dispose(); @@ -392,29 +392,31 @@ interface IActiveTestNotebookEditorDelegate extends IActiveNotebookEditorDelegat visibleRanges: ICellRange[]; } -export async function withTestNotebook(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveTestNotebookEditorDelegate, viewModel: NotebookViewModel, accessor: TestInstantiationService) => Promise | R, accessor?: TestInstantiationService): Promise { - const disposables = new DisposableStore(); +export async function withTestNotebook(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveTestNotebookEditorDelegate, viewModel: NotebookViewModel, disposables: DisposableStore, accessor: TestInstantiationService) => Promise | R, accessor?: TestInstantiationService): Promise { + const disposables: DisposableStore = new DisposableStore(); const instantiationService = accessor ?? setupInstantiationService(disposables); - const notebookEditor = _createTestNotebookEditor(instantiationService, cells); + const notebookEditor = _createTestNotebookEditor(instantiationService, disposables, cells); return runWithFakedTimers({ useFakeTimers: true }, async () => { - const res = await callback(notebookEditor.editor, notebookEditor.viewModel, instantiationService); + const res = await callback(notebookEditor.editor, notebookEditor.viewModel, disposables, instantiationService); if (res instanceof Promise) { res.finally(() => { notebookEditor.editor.dispose(); notebookEditor.viewModel.dispose(); + notebookEditor.editor.textModel.dispose(); disposables.dispose(); }); } else { notebookEditor.editor.dispose(); notebookEditor.viewModel.dispose(); + notebookEditor.editor.textModel.dispose(); disposables.dispose(); } return res; }); } -export function createNotebookCellList(instantiationService: TestInstantiationService, viewContext?: ViewContext) { +export function createNotebookCellList(instantiationService: TestInstantiationService, disposables: DisposableStore, viewContext?: ViewContext) { const delegate: IListVirtualDelegate = { getHeight(element: CellViewModel) { return element.getHeight(17); }, getTemplateId() { return 'template'; } @@ -427,11 +429,11 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe disposeTemplate() { } }; - const cellList: NotebookCellList = instantiationService.createInstance( + const cellList: NotebookCellList = disposables.add(instantiationService.createInstance( NotebookCellList, 'NotebookCellList', DOM.$('container'), - viewContext ?? new ViewContext(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false), new NotebookEventDispatcher(), () => ({} as IBaseCellEditorOptions)), + viewContext ?? new ViewContext(disposables.add(new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false)), disposables.add(new NotebookEventDispatcher()), () => ({} as IBaseCellEditorOptions)), delegate, [renderer], instantiationService.get(IContextKeyService), @@ -439,7 +441,7 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe supportDynamicHeights: true, multipleSelectionSupport: true, } - ); + )); return cellList; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index d751cf35bdd..8c9da329807 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -37,7 +37,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/common/editor'; import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; -import { getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { ITOCEntry, getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; @@ -1242,6 +1242,11 @@ export class SettingsEditor2 extends EditorPane { return undefined; } + private refreshModels(resolvedSettingsRoot: ITOCEntry) { + this.settingsTreeModel.update(resolvedSettingsRoot); + this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + } + private async onConfigUpdate(keys?: ReadonlySet, forceRefresh = false, schemaChange = false): Promise { if (keys && this.settingsTreeModel) { return this.updateElementsByKey(keys); @@ -1340,7 +1345,7 @@ export class SettingsEditor2 extends EditorPane { this.searchResultModel?.updateChildren(); if (this.settingsTreeModel) { - this.settingsTreeModel.update(resolvedSettingsRoot); + this.refreshModels(resolvedSettingsRoot); if (schemaChange && !!this.searchResultModel) { // If an extension's settings were just loaded and a search is active, retrigger the search so it shows up @@ -1351,8 +1356,7 @@ export class SettingsEditor2 extends EditorPane { this.renderTree(undefined, forceRefresh); } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, this.workspaceTrustManagementService.isWorkspaceTrusted()); - this.settingsTreeModel.update(resolvedSettingsRoot); - this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + this.refreshModels(resolvedSettingsRoot); // Don't restore the cached state if we already have a query value from calling _setOptions(). const cachedState = !this.viewState.query ? this.restoreCachedState() : undefined; diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index beff3204133..1dc6c5919bf 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -36,6 +36,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { CommandInformationResult, IAiRelatedInformationService, RelatedInformationType } from 'vs/workbench/services/aiRelatedInformation/common/aiRelatedInformation'; +import { CHAT_OPEN_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -181,7 +182,7 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce if (info) { additionalPicks.push({ label: localize('askXInChat', "Ask {0}: {1}", info.displayName, filter), - commandId: ASK_QUICK_QUESTION_ACTION_ID, + commandId: this.configuration.experimental.askChatLocation === 'quickChat' ? ASK_QUICK_QUESTION_ACTION_ID : CHAT_OPEN_ACTION_ID, args: [filter] }); } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 2289a2ece04..be89affe5e2 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -1465,7 +1465,7 @@ namespace ChangeLocalPortAction { function validateInput(tunnelService: ITunnelService, value: string, canElevate: boolean): { content: string; severity: Severity } | null { if (!value.match(/^[0-9]+$/)) { - return { content: invalidPortString, severity: Severity.Error }; + return { content: nls.localize('remote.tunnelsView.portShouldBeNumber', "Local port should be a number."), severity: Severity.Error }; } else if (Number(value) >= maxPortNumber) { return { content: invalidPortNumberString, severity: Severity.Error }; } else if (canElevate && tunnelService.isPortPrivileged(Number(value))) { diff --git a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts index 145c7c81a1e..eee900e11c5 100644 --- a/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts +++ b/src/vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution.ts @@ -112,7 +112,7 @@ export class RemoteTunnelWorkbenchContribution extends Disposable implements IWo ) { super(); - this.logger = this._register(loggerService.createLogger(LOG_ID, { name: LOGGER_NAME })); + this.logger = this._register(loggerService.createLogger(joinPath(environmentService.logsHome, `${LOG_ID}.log`), { id: LOG_ID, name: LOGGER_NAME })); this.connectionStateContext = REMOTE_TUNNEL_CONNECTION_STATE.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/contrib/scm/common/quickDiffService.ts b/src/vs/workbench/contrib/scm/common/quickDiffService.ts index 137913c91b3..50e26ed2907 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiffService.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiffService.ts @@ -43,6 +43,10 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { private readonly _onDidChangeQuickDiffProviders = this._register(new Emitter()); readonly onDidChangeQuickDiffProviders = this._onDidChangeQuickDiffProviders.event; + // It is common to get many requests for the same resource back to back (ex. when editing a file) + // Cache the last resource so to avoid unneeded extension host round trips. + private cachedOriginalResource: { uri: URI; resources: Map } | undefined; + constructor(@IUriIdentityService private readonly uriIdentityService: IUriIdentityService) { super(); } @@ -62,6 +66,19 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { return !!diff.originalResource && (typeof diff.label === 'string') && (typeof diff.isSCM === 'boolean'); } + private getOriginalResourceFromCache(provider: string, uri: URI): URI | undefined { + if (this.cachedOriginalResource?.uri.toString() === uri.toString()) { + return this.cachedOriginalResource.resources.get(provider); + } + return undefined; + } + + private updateOriginalResourceCache(uri: URI, quickDiffs: QuickDiff[]) { + if (this.cachedOriginalResource?.uri.toString() !== uri.toString()) { + this.cachedOriginalResource = { uri, resources: new Map(quickDiffs.map(diff => ([diff.label, diff.originalResource]))) }; + } + } + async getQuickDiffs(uri: URI, language: string = '', isSynchronized: boolean = false): Promise { const providers = Array.from(this.quickDiffProviders) .filter(provider => !provider.rootUri || this.uriIdentityService.extUri.isEqualOrParent(uri, provider.rootUri)) @@ -70,12 +87,14 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { const diffs = await Promise.all(providers.map(async provider => { const scoreValue = provider.selector ? score(provider.selector, uri, language, isSynchronized, undefined, undefined) : 10; const diff: Partial = { - originalResource: scoreValue > 0 ? await provider.getOriginalResource(uri) ?? undefined : undefined, + originalResource: scoreValue > 0 ? (this.getOriginalResourceFromCache(provider.label, uri) ?? await provider.getOriginalResource(uri) ?? undefined) : undefined, label: provider.label, isSCM: provider.isSCM }; return diff; })); - return diffs.filter(this.isQuickDiff); + const quickDiffs = diffs.filter(this.isQuickDiff); + this.updateOriginalResourceCache(uri, quickDiffs); + return quickDiffs; } } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index 14b1636c0b8..3e23fe5fa73 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -5,6 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IMatch } from 'vs/base/common/filters'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ResourceSet } from 'vs/base/common/map'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/base/common/themables'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -128,7 +129,8 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider { await result.asyncResults; - return this.searchModel.searchResult.matches().filter(e => result.syncResults.indexOf(e) === -1); + const syncResultURIs = new ResourceSet(result.syncResults.map(e => e.resource)); + return this.searchModel.searchResult.matches().filter(e => !syncResultURIs.has(e.resource)); }; return { syncResults: this.searchModel.searchResult.matches(), diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts index b517ae9e2b3..8306eb4af7c 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts @@ -10,6 +10,10 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { category } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { TEXT_SEARCH_QUICK_ACCESS_PREFIX } from 'vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { getSelectionTextFromEditor } from 'vs/workbench/contrib/search/browser/searchView'; registerAction2(class TextSearchQuickAccessAction extends Action2 { @@ -29,6 +33,28 @@ registerAction2(class TextSearchQuickAccessAction extends Action2 { override async run(accessor: ServicesAccessor, match: RenderableMatch | undefined): Promise { const quickInputService = accessor.get(IQuickInputService); - quickInputService.quickAccess.show(TEXT_SEARCH_QUICK_ACCESS_PREFIX); + const searchText = getSearchText(accessor) ?? ''; + quickInputService.quickAccess.show(TEXT_SEARCH_QUICK_ACCESS_PREFIX + searchText); } }); + +function getSearchText(accessor: ServicesAccessor): string | null { + const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); + + const activeEditor: IEditor = editorService.activeTextEditorControl as IEditor; + if (!activeEditor) { + return null; + } + if (!activeEditor.hasTextFocus()) { + return null; + } + + // only happen if it would also happen for the search view + const seedSearchStringFromSelection = configurationService.getValue('editor.find.seedSearchStringFromSelection'); + if (!seedSearchStringFromSelection) { + return null; + } + + return getSelectionTextFromEditor(false, activeEditor); +} diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index dd150169376..019f4a9f4ae 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1288,45 +1288,12 @@ export class SearchView extends ViewPane { } } - if (!isCodeEditor(editor) || !editor.hasModel()) { + if (!editor) { return null; } - const range = editor.getSelection(); - if (!range) { - return null; - } - - if (range.isEmpty() && this.searchConfig.seedWithNearestWord && allowUnselectedWord) { - const wordAtPosition = editor.getModel().getWordAtPosition(range.getStartPosition()); - if (wordAtPosition) { - return wordAtPosition.word; - } - } - - if (!range.isEmpty()) { - let searchText = ''; - for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { - let lineText = editor.getModel().getLineContent(i); - if (i === range.endLineNumber) { - lineText = lineText.substring(0, range.endColumn - 1); - } - - if (i === range.startLineNumber) { - lineText = lineText.substring(range.startColumn - 1); - } - - if (i !== range.startLineNumber) { - lineText = '\n' + lineText; - } - - searchText += lineText; - } - - return searchText; - } - - return null; + const allowUnselected = this.searchConfig.seedWithNearestWord && allowUnselectedWord; + return getSelectionTextFromEditor(allowUnselected, editor); } private showsFileTypes(): boolean { @@ -2076,6 +2043,9 @@ export class SearchView extends ViewPane { } private _saveSearchHistoryService() { + if (this.searchWidget === undefined) { + return; + } const history: ISearchHistoryValues = Object.create(null); const searchHistory = this.searchWidget.getSearchHistory(); @@ -2175,3 +2145,44 @@ export function getEditorSelectionFromMatch(element: FileMatchOrMatch, viewModel } return undefined; } + +export function getSelectionTextFromEditor(allowUnselectedWord: boolean, editor: IEditor): string | null { + + if (!isCodeEditor(editor) || !editor.hasModel()) { + return null; + } + + const range = editor.getSelection(); + if (!range) { + return null; + } + + if (range.isEmpty()) { + if (allowUnselectedWord) { + const wordAtPosition = editor.getModel().getWordAtPosition(range.getStartPosition()); + return wordAtPosition?.word ?? null; + } else { + return null; + } + } + + let searchText = ''; + for (let i = range.startLineNumber; i <= range.endLineNumber; i++) { + let lineText = editor.getModel().getLineContent(i); + if (i === range.endLineNumber) { + lineText = lineText.substring(0, range.endColumn - 1); + } + + if (i === range.startLineNumber) { + lineText = lineText.substring(range.startColumn - 1); + } + + if (i !== range.startLineNumber) { + lineText = '\n' + lineText; + } + + searchText += lineText; + } + + return searchText; +} diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index ca0a09f0c31..715554ed96a 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -27,7 +27,7 @@ import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentitySe import { ILabelService } from 'vs/platform/label/common/label'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; import { ICellMatch, IFileMatchWithCells, contentMatchesToTextSearchMatches, webviewMatchesToTextSearchMatches } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; @@ -37,6 +37,9 @@ import { FindMatch, IReadonlyTextBuffer } from 'vs/editor/common/model'; import { ResourceMap, ResourceSet } from 'vs/base/common/map'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const nullEvent = new class { id: number = -1; @@ -160,14 +163,10 @@ suite('SearchModel', () => { function canceleableSearchService(tokenSource: CancellationTokenSource): ISearchService { return { - textSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { + textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { token?.onCancellationRequested(() => tokenSource.cancel()); - return new Promise(resolve => { - queueMicrotask(() => { - resolve({}); - }); - }); + return this.textSearchSplitSyncAsync(query, token, onProgress).asyncResults; }, fileSearch(query: IFileQuery, token?: CancellationToken): Promise { token?.onCancellationRequested(() => tokenSource.cancel()); @@ -186,7 +185,10 @@ suite('SearchModel', () => { }, asyncResults: new Promise(resolve => { queueMicrotask(() => { - resolve({}); + resolve({ + results: [], + messages: [] + }); }); }) }; @@ -460,7 +462,7 @@ suite('SearchModel', () => { const target1 = sinon.stub().returns(nullEvent); instantiationService.stub(ITelemetryService, 'publicLog', target1); - instantiationService.stub(ISearchService, searchServiceWithError(new Error('error'))); + instantiationService.stub(ISearchService, searchServiceWithError(new Error('This error should be thrown by this test.'))); instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject = instantiationService.createInstance(SearchModel); @@ -581,6 +583,8 @@ suite('SearchModel', () => { function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IEditorService, new TestEditorService()); return instantiationService.createInstance(NotebookEditorWidgetService); } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 50500494d9a..923d74b550d 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -26,12 +26,15 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLabelService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; import { ICellMatch, IFileMatchWithCells } from 'vs/workbench/contrib/search/browser/searchNotebookHelpers'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { addToSearchResult, createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -553,6 +556,8 @@ suite('SearchResult', () => { function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IEditorService, new TestEditorService()); return instantiationService.createInstance(NotebookEditorWidgetService); } diff --git a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts index 026df47ea9c..71e860b9f75 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts @@ -9,15 +9,18 @@ import { IModelService } from 'vs/editor/common/services/model'; import { ModelService } from 'vs/editor/common/services/modelService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; import { SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileMatch } from 'vs/workbench/services/search/common/search'; -import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; export function createFileUriFromPathFromRoot(path?: string): URI { const rootName = getRootName(); @@ -50,6 +53,8 @@ export function stubModelService(instantiationService: TestInstantiationService) export function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IEditorService, new TestEditorService()); return instantiationService.createInstance(NotebookEditorWidgetService); } diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 279ee49e361..f73f00799f6 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -26,9 +26,12 @@ import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -215,6 +218,8 @@ suite('Search - Viewlet', () => { function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); + instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IEditorService, new TestEditorService()); return instantiationService.createInstance(NotebookEditorWidgetService); } }); diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts index 4a6b5d4d598..39b51590658 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorModel.ts @@ -16,6 +16,7 @@ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textMo import { SearchEditorWorkingCopyTypeId } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; +import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search'; export type SearchEditorData = { resultsModel: ITextModel; configurationModel: SearchConfigurationModel }; @@ -65,7 +66,7 @@ class SearchEditorModelFactory { } return Promise.resolve({ - resultsModel: modelService.getModel(resource) ?? modelService.createModel('', languageService.createById('search-result'), resource), + resultsModel: modelService.getModel(resource) ?? modelService.createModel('', languageService.createById(SEARCH_RESULT_LANGUAGE_ID), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -98,7 +99,7 @@ class SearchEditorModelFactory { } return Promise.resolve({ - resultsModel: modelService.createModel(contents ?? '', languageService.createById('search-result'), resource), + resultsModel: modelService.createModel(contents ?? '', languageService.createById(SEARCH_RESULT_LANGUAGE_ID), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -132,7 +133,7 @@ class SearchEditorModelFactory { const { text, config } = await instantiationService.invokeFunction(parseSavedSearchEditor, existingFile); return ({ - resultsModel: modelService.createModel(text ?? '', languageService.createById('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.createById(SEARCH_RESULT_LANGUAGE_ID), resource), configurationModel: new SearchConfigurationModel(config) }); })(); @@ -149,7 +150,7 @@ class SearchEditorModelFactory { if (!model && backup) { const factory = await createTextBufferFactoryFromStream(backup.value); - model = modelService.createModel(factory, languageService.createById('search-result'), resource); + model = modelService.createModel(factory, languageService.createById(SEARCH_RESULT_LANGUAGE_ID), resource); } if (model) { @@ -157,7 +158,7 @@ class SearchEditorModelFactory { const { text, config } = parseSerializedSearchEditor(existingFile); modelService.destroyModel(resource); return ({ - resultsModel: modelService.createModel(text ?? '', languageService.createById('search-result'), resource), + resultsModel: modelService.createModel(text ?? '', languageService.createById(SEARCH_RESULT_LANGUAGE_ID), resource), configurationModel: new SearchConfigurationModel(config) }); } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 406ff2a20b1..e74a499f9c0 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -16,6 +16,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { generateUuid } from 'vs/base/common/uuid'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; class SimpleSnippetService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -83,9 +84,11 @@ suite('SnippetsService', function () { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('snippet completions - simple', function () { - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, '', 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -96,7 +99,7 @@ suite('SnippetsService', function () { test('snippet completions - simple 2', async function () { - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'hello ', 'fooLang')); await provider.provideCompletionItems(model, new Position(1, 6) /* hello| */, context)!.then(result => { @@ -112,7 +115,7 @@ suite('SnippetsService', function () { test('snippet completions - with prefix', function () { - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'bar', 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { @@ -150,7 +153,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'bar-bar', 'fooLang')); await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { @@ -222,7 +225,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); let model = instantiateTextModel(instantiationService, '\t { @@ -259,7 +262,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, '\n\t\n>/head>', 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -293,7 +296,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, '', 'fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { @@ -322,7 +325,7 @@ suite('SnippetsService', function () { SnippetSource.User, generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'p-', 'fooLang')); @@ -349,7 +352,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', 'fooLang')); const result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -370,7 +373,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, ':', 'fooLang')); const result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; @@ -391,7 +394,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'template', 'fooLang')); const result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -416,7 +419,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', 'fooLang')); const result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; @@ -425,7 +428,7 @@ suite('SnippetsService', function () { }); test('issue #61296: VS code freezes when editing CSS file with emoji', async function () { - const languageConfigurationService = new TestLanguageConfigurationService(); + const languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); disposables.add(languageConfigurationService.register('fooLang', { wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g })); @@ -463,7 +466,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = disposables.add(instantiateTextModel(instantiationService, 'a ', 'fooLang')); const result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -494,7 +497,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); let model = instantiateTextModel(instantiationService, ' <', 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -526,7 +529,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); let model = instantiateTextModel(instantiationService, 'not wordFoo bar', 'fooLang'); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; @@ -570,7 +573,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, 'filler e KEEP ng filler', 'fooLang'); const result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; @@ -583,7 +586,7 @@ suite('SnippetsService', function () { }); test('Snippet will replace auto-closing pair if specified in prefix', async function () { - const languageConfigurationService = new TestLanguageConfigurationService(); + const languageConfigurationService = disposables.add(new TestLanguageConfigurationService()); disposables.add(languageConfigurationService.register('fooLang', { brackets: [ ['{', '}'], @@ -631,7 +634,7 @@ suite('SnippetsService', function () { generateUuid() )]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, ' ci', 'fooLang'); const result = await provider.provideCompletionItems(model, new Position(1, 4), context)!; @@ -652,7 +655,7 @@ suite('SnippetsService', function () { // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid()) ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, '\'\'', 'fooLang'); const result = await provider.provideCompletionItems( @@ -674,7 +677,7 @@ suite('SnippetsService', function () { new Snippet(false, ['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid()) ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, '\'\'', 'fooLang'); @@ -695,7 +698,7 @@ suite('SnippetsService', function () { new Snippet(false, ['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()), ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, '\'hellot\'', 'fooLang'); const result = await provider.provideCompletionItems( @@ -716,7 +719,7 @@ suite('SnippetsService', function () { new Snippet(false, ['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()), ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, ')*&^', 'fooLang'); const result = await provider.provideCompletionItems( @@ -736,7 +739,7 @@ suite('SnippetsService', function () { new Snippet(false, ['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User, generateUuid()), ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, 'foobar', 'fooLang'); const result = await provider.provideCompletionItems( @@ -756,7 +759,7 @@ suite('SnippetsService', function () { ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, 'function abc(w)', 'fooLang'); const result = await provider.provideCompletionItems( model, @@ -775,7 +778,7 @@ suite('SnippetsService', function () { new Snippet(false, ['fooLang'], 'div#', 'div#', '', 'div#', '', SnippetSource.User, generateUuid()), ]); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const model = instantiateTextModel(instantiationService, 'di', 'fooLang'); const result = await provider.provideCompletionItems( model, @@ -804,8 +807,8 @@ suite('SnippetsService', function () { snippetService = new SimpleSnippetService([ new Snippet(false, ['fooLang'], 'foo', 'Foo- Bar', '', 'Foo', '', SnippetSource.User, generateUuid()), ]); - const model = instantiateTextModel(instantiationService, ' bar', 'fooLang'); - const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); + const model = disposables.add(instantiateTextModel(instantiationService, ' bar', 'fooLang')); + const provider = new SnippetCompletionProvider(languageService, snippetService, disposables.add(new TestLanguageConfigurationService())); const result = await provider.provideCompletionItems( model, new Position(1, 8), diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 820c2bce781..a252d8e4814 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -229,6 +229,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _onDidChangeTaskSystemInfo: Emitter = new Emitter(); private _willRestart: boolean = false; public onDidChangeTaskSystemInfo: Event = this._onDidChangeTaskSystemInfo.event; + private _onDidReconnectToTasks: Emitter = new Emitter(); + public onDidReconnectToTasks: Event = this._onDidReconnectToTasks.event; constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -342,11 +344,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this._terminalService.getReconnectedTerminals('Task')?.length) { this._attemptTaskReconnection(); } else { - this._register(this._terminalService.onDidChangeConnectionState(() => { + this._terminalService.whenConnected.then(() => { if (this._terminalService.getReconnectedTerminals('Task')?.length) { this._attemptTaskReconnection(); + } else { + this._tasksReconnected = true; } - })); + }); } this._upgrade(); } @@ -384,6 +388,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this.getWorkspaceTasks(TaskRunSource.Reconnect).then(async () => { this._tasksReconnected = await this._reconnectTasks(); + this._onDidReconnectToTasks.fire(); }); } @@ -1862,6 +1867,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } if (executeResult.kind === TaskExecuteKind.Active) { const active = executeResult.active; + if (active && active.same && runSource === TaskRunSource.FolderOpen || runSource === TaskRunSource.Reconnect) { + // ignore, the task is already active, likely from being reconnected or from folder open. + this._logService.debug('Ignoring task that is already active', executeResult.task); + return executeResult.promise; + } if (active && active.same) { if (this._taskSystem?.isTaskVisible(executeResult.task)) { const message = nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active.', executeResult.task.getQualifiedLabel()); @@ -2760,6 +2770,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private async _runTaskCommand(filter?: string | ITaskIdentifier): Promise { + if (!this._tasksReconnected) { + return; + } if (!filter) { return this._doRunTaskCommand(); } @@ -2877,7 +2890,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (executeResult) { return this._handleExecuteResult(executeResult); } else { - this._doRunTaskCommand(); return Promise.resolve(undefined); } }); @@ -3034,6 +3046,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private _runBuildCommand(): void { + if (!this._tasksReconnected) { + return; + } return this._runTaskGroupCommand(TaskGroup.Build, { fetching: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...'), select: nls.localize('TaskService.pickBuildTask', 'Select the build task to run'), diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 0aaa08a838d..55ec14e22a0 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -28,9 +28,11 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService private readonly _logService: ILogService) { super(); - if (this._workspaceTrustManagementService.isWorkspaceTrusted()) { - this._tryRunTasks(); - } + this._taskService.onDidReconnectToTasks((() => { + if (this._workspaceTrustManagementService.isWorkspaceTrusted()) { + this._tryRunTasks(); + } + })); this._register(this._workspaceTrustManagementService.onDidChangeTrust(async trusted => { if (trusted) { await this._tryRunTasks(); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index ab75308843d..263eee32445 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -936,7 +936,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); if (trigger === Triggers.reconnect && !!terminal.xterm) { const bufferLines = []; - const bufferReverseIterator = terminal.xterm.getBufferReverseIterator(); const startRegex = new RegExp(watchingProblemMatcher.beginPatterns.map(pattern => pattern.source).join('|')); for (const nextLine of bufferReverseIterator) { @@ -945,8 +944,16 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { break; } } + let delayer: Async.Delayer | undefined = undefined; for (let i = bufferLines.length - 1; i >= 0; i--) { watchingProblemMatcher.processLine(bufferLines[i]); + if (!delayer) { + delayer = new Async.Delayer(3000); + } + delayer.trigger(() => { + watchingProblemMatcher.forceDelivery(); + delayer = undefined; + }); } } } else { diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 42236fee046..c96cba1125d 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -64,6 +64,7 @@ export interface IWorkspaceFolderTaskResult extends IWorkspaceTaskResult { export interface ITaskService { readonly _serviceBrand: undefined; onDidStateChange: Event; + onDidReconnectToTasks: Event; supportsMultipleTaskExecutions: boolean; configureAction(): Action; 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 687b9ee025a..4d1479ca9f5 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -47,7 +47,7 @@ fi # Apply EnvironmentVariableCollections if needed if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_REPLACE" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_REPLACE" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -56,7 +56,7 @@ if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then builtin unset VSCODE_ENV_REPLACE fi if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_PREPEND" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_PREPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -65,7 +65,7 @@ if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then builtin unset VSCODE_ENV_PREPEND fi if [ -n "${VSCODE_ENV_APPEND:-}" ]; then - IFS=':' read -ra ADDR <<< "$VSCODE_ENV_APPEND" + IFS=':' read -ra ADDR <<<"$VSCODE_ENV_APPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -95,13 +95,52 @@ __vsc_get_trap() { builtin printf '%s' "${terms[2]:-}" } +__vsc_command_available() { + builtin local trash + trash=$(builtin command -v "$1" 2>&1) + builtin return $? +} + +# We provide two faster escaping functions here. +# The first one escapes each byte 0xab into '\xab', which is most scalable and has promising runtime +# efficiency, except that it relies on external commands od and tr. +# The second one is much faster and has zero dependency, except that it escapes only +# '\\' -> '\\\\' and ';' -> '\x3b' and scales up badly when more patterns are needed. +# We default to use the first function if od and tr are available, and fallback to the second otherwise. +if __vsc_command_available od && __vsc_command_available tr; then + __vsc_escape_value_fast() { + builtin local out + # -An removes line number + # -v do not use * to mark line suppression + # -tx1 prints each byte as two-digit hex + # tr -d '\n' concats all output lines + out=$(od -An -vtx1 <<<"$1" | tr -d '\n') + out=${out// /\\x} + # <<<"$1" prepends a trailing newline already, so we don't need to printf '%s\n' + builtin printf '%s' "${out}" + } +else + __vsc_escape_value_fast() { + builtin local LC_ALL=C out + out=${1//\\/\\\\} + out=${out//;/\\x3b} + builtin printf '%s\n' "${out}" + } +fi + # The property (P) and command (E) codes embed values which require escaping. # Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. __vsc_escape_value() { + # If the input being too large, switch to the faster function + if [ "${#1}" -ge 2000 ]; then + __vsc_escape_value_fast "$1" + builtin return + fi + # Process text byte by byte, not by codepoint. builtin local LC_ALL=C str="${1}" i byte token out='' - for (( i=0; i < "${#str}"; ++i )); do + for ((i = 0; i < "${#str}"; ++i)); do byte="${str:$i:1}" # Escape backslashes and semi-colons diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8e58f90c8fc..3b01c31dff1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -61,6 +61,7 @@ import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/cap import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Iterable } from 'vs/base/common/iterator'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewOnLastLine } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -479,7 +480,7 @@ export function registerTerminalActions() { id: TerminalCommandId.Focus, title: terminalStrings.focus, keybinding: { - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.accessibleBufferOnLastLine), + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, accessibleViewOnLastLine, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Terminal)), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib }, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index bf6dc33b430..5c4b8b19b1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -5,7 +5,7 @@ import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -267,6 +267,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } private _initialRelativeSizes: number[] | undefined; + private _visible: boolean = false; private readonly _onDidDisposeInstance: Emitter = this._register(new Emitter()); readonly onDidDisposeInstance = this._onDidDisposeInstance.event; @@ -300,6 +301,12 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this.attachToElement(this._container); } this._onPanelOrientationChanged.fire(this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL); + this._register(toDisposable(() => { + if (this._container && this._groupElement) { + this._container.removeChild(this._groupElement); + this._groupElement = undefined; + } + })); } addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, parentTerminalId?: number): void { @@ -328,13 +335,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } override dispose(): void { - super.dispose(); - if (this._container && this._groupElement) { - this._container.removeChild(this._groupElement); - this._groupElement = undefined; - } this._terminalInstances = []; this._onInstancesChanged.fire(); + super.dispose(); } get activeInstance(): ITerminalInstance | undefined { @@ -481,10 +484,6 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { const orientation = this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; this._splitPaneContainer = this._instantiationService.createInstance(SplitPaneContainer, this._groupElement, orientation); this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance, this._activeInstanceIndex + 1)); - if (this._initialRelativeSizes) { - this.resizePanes(this._initialRelativeSizes); - this._initialRelativeSizes = undefined; - } } } @@ -518,6 +517,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { } setVisible(visible: boolean): void { + this._visible = visible; if (this._groupElement) { this._groupElement.style.display = visible ? '' : 'none'; } @@ -549,6 +549,10 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._onPanelOrientationChanged.fire(this._splitPaneContainer.orientation); } this._splitPaneContainer.layout(width, height); + if (this._initialRelativeSizes && this._visible) { + this.resizePanes(this._initialRelativeSizes); + this._initialRelativeSizes = undefined; + } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index fa3a6cf51d3..e37b1ba4d2b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -503,30 +503,23 @@ export class TerminalService extends Disposable implements ITerminalService { private async _recreateTerminalGroup(tabLayout: IRawTerminalTabLayoutInfo, terminalLayouts: IRawTerminalInstanceLayoutInfo[]): Promise { let lastInstance: Promise | undefined; - let group: Promise | undefined; for (const terminalLayout of terminalLayouts) { const attachPersistentProcess = terminalLayout.terminal!; if (this._lifecycleService.startupKind !== StartupKind.ReloadedWindow && attachPersistentProcess.type === 'Task') { continue; } mark(`code/terminal/willRecreateTerminal/${attachPersistentProcess.id}-${attachPersistentProcess.pid}`); - if (!lastInstance) { - // create group and terminal - lastInstance = this.createTerminal({ - config: { attachPersistentProcess }, - location: TerminalLocation.Panel - }); - group = lastInstance.then(instance => this._terminalGroupService.getGroupForInstance(instance)); - } else { - // add split terminals to this group - lastInstance = this.createTerminal({ - config: { attachPersistentProcess }, - location: { parentTerminal: lastInstance } - }); - } - mark(`code/terminal/didRecreateTerminal/${attachPersistentProcess.id}-${attachPersistentProcess.pid}`); + lastInstance = this.createTerminal({ + config: { attachPersistentProcess }, + location: lastInstance ? { parentTerminal: lastInstance } : TerminalLocation.Panel + }); + lastInstance.then(() => mark(`code/terminal/didRecreateTerminal/${attachPersistentProcess.id}-${attachPersistentProcess.pid}`)); } - group?.then(g => g?.resizePanes(tabLayout.terminals.map(terminal => terminal.relativeSize))); + const group = lastInstance?.then(instance => { + const g = this._terminalGroupService.getGroupForInstance(instance); + g?.resizePanes(tabLayout.terminals.map(terminal => terminal.relativeSize)); + return g; + }); return group; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index db205d31f34..b56979d0da9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -244,9 +244,7 @@ export class TerminalViewPane extends ViewPane { super(action.id, action.label, action.class, action.enabled); this.checked = action.checked; this.tooltip = action.tooltip; - } - override dispose(): void { - action.dispose(); + this._register(action); } override async run() { const instance = that._terminalGroupService.activeInstance; diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts index c2de2c4d1d6..9e6a23950b0 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Widget } from 'vs/base/browser/ui/widget'; import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets'; @@ -37,10 +37,6 @@ export class TerminalHover extends Disposable implements ITerminalWidget { super(); } - override dispose() { - super.dispose(); - } - attach(container: HTMLElement): void { const showLinkHover = this._configurationService.getValue(TerminalSettingId.ShowLinkHover); if (!showLinkHover) { @@ -62,7 +58,7 @@ export class TerminalHover extends Disposable implements ITerminalWidget { } class CellHoverTarget extends Widget implements IHoverTarget { - private _domNode: HTMLElement | undefined; + private _domNode: HTMLElement; private readonly _targetElements: HTMLElement[] = []; get targetElements(): readonly HTMLElement[] { return this._targetElements; } @@ -122,10 +118,6 @@ class CellHoverTarget extends Widget implements IHoverTarget { } container.appendChild(this._domNode); - } - - override dispose(): void { - this._domNode?.parentElement?.removeChild(this._domNode); - super.dispose(); + this._register(toDisposable(() => this._domNode?.remove())); } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 450580f9a50..ac1a2096320 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -953,7 +953,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach this.raw.write(data); } - public override dispose(): void { + override dispose(): void { this._anyTerminalFocusContextKey.reset(); this._anyFocusedTerminalHasSelection.reset(); this._onDidDispose.fire(); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 2c67eda18cb..efe0d3be971 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -399,7 +399,6 @@ export const enum TerminalCommandId { OpenWebLink = 'workbench.action.terminal.openUrlLink', RunRecentCommand = 'workbench.action.terminal.runRecentCommand', FocusAccessibleBuffer = 'workbench.action.terminal.focusAccessibleBuffer', - NavigateAccessibleBuffer = 'workbench.action.terminal.navigateAccessibleBuffer', AccessibleBufferGoToNextCommand = 'workbench.action.terminal.accessibleBufferGoToNextCommand', AccessibleBufferGoToPreviousCommand = 'workbench.action.terminal.accessibleBufferGoToPreviousCommand', CopyLastCommandOutput = 'workbench.action.terminal.copyLastCommandOutput', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ea7c0335df5..cdb90775373 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -299,7 +299,7 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.TerminalTitleSeparator]: { 'type': 'string', 'default': ' - ', - 'markdownDescription': localize("terminal.integrated.tabs.separator", "Separator used by {0} and {0}.", `\`${TerminalSettingId.TerminalTitle}\``, `\`${TerminalSettingId.TerminalDescription}\``) + 'markdownDescription': localize("terminal.integrated.tabs.separator", "Separator used by {0} and {1}.", `\`#${TerminalSettingId.TerminalTitle}#\``, `\`#${TerminalSettingId.TerminalDescription}#\``) }, [TerminalSettingId.TerminalTitle]: { 'type': 'string', @@ -541,7 +541,7 @@ const terminalConfiguration: IConfigurationNode = { default: 'never' }, [TerminalSettingId.CustomGlyphs]: { - description: localize('terminal.integrated.customGlyphs', "Whether to draw custom glyphs for block element and box drawing characters instead of using the font, which typically yields better rendering with continuous lines. Note that this doesn't work when {0} is disabled.", `\`#${TerminalSettingId.GpuAcceleration}#\``), + markdownDescription: localize('terminal.integrated.customGlyphs', "Whether to draw custom glyphs for block element and box drawing characters instead of using the font, which typically yields better rendering with continuous lines. Note that this doesn't work when {0} is disabled.", `\`#${TerminalSettingId.GpuAcceleration}#\``), type: 'boolean', default: true }, diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index ce4f7a4168c..1073cf8767d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -49,12 +49,6 @@ export namespace TerminalContextKeys { /** Whether any terminal is focused, including detached terminals used in other UI. */ export const focusInAny = new RawContextKey(TerminalContextKeyStrings.FocusInAny, false, localize('terminalFocusInAnyContextKey', "Whether any terminal is focused, including detached terminals used in other UI.")); - /** Whether the accessible buffer is focused. */ - export const accessibleBufferFocus = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferFocus, false, localize('terminalAccessibleBufferFocusContextKey', "Whether the terminal accessible buffer is focused.")); - - /** Whether the accessible buffer focus is on the last line. */ - export const accessibleBufferOnLastLine = new RawContextKey(TerminalContextKeyStrings.AccessibleBufferOnLastLine, false, localize('terminalAccessibleBufferOnLastLineContextKey', "Whether the accessible buffer focus is on the last line.")); - /** Whether a terminal in the editor area is focused. */ export const editorFocus = new RawContextKey(TerminalContextKeyStrings.EditorFocus, false, localize('terminalEditorFocusContextKey', "Whether a terminal in the editor area is focused.")); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts index 53bc70995a9..14678778467 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { IMarker, Terminal } from 'xterm'; -export class BufferContentTracker { +export class BufferContentTracker extends Disposable { /** * Marks the last part of the buffer that was cached */ @@ -27,6 +28,7 @@ export class BufferContentTracker { private readonly _xterm: Pick & { raw: Terminal }, @ITerminalLogService private readonly _logService: ITerminalLogService, @IConfigurationService private readonly _configurationService: IConfigurationService) { + super(); } reset(): void { @@ -44,7 +46,7 @@ export class BufferContentTracker { this._removeViewportContent(); this._updateCachedContent(); this._updateViewportContent(); - this._lastCachedMarker = this._xterm.raw.registerMarker(); + this._lastCachedMarker = this._register(this._xterm.raw.registerMarker()); this._logService.debug('Buffer content tracker: set ', this._lines.length, ' lines'); } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index b0596b2d2b0..29783e7170b 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -7,25 +7,27 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalSettingId, terminalTabFocusModeContextKey } from 'vs/platform/terminal/common/terminal'; -import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; +import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService, NavigationType } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; +import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; import { TerminalAccessibleContentProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp'; -import { AccessibleBufferWidget, NavigationType } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer'; import { TextAreaSyncAddon } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon'; import type { Terminal } from 'xterm'; - +import { Position } from 'vs/editor/common/core/position'; +import { ICommandWithEditorLine, TerminalAccessibleBufferProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider'; class TextAreaSyncContribution extends DisposableStore implements ITerminalContribution { static readonly ID = 'terminal.textAreaSync'; @@ -48,56 +50,113 @@ class TextAreaSyncContribution extends DisposableStore implements ITerminalContr } registerTerminalContribution(TextAreaSyncContribution.ID, TextAreaSyncContribution); -class AccessibleBufferContribution extends DisposableStore implements ITerminalContribution { - static readonly ID = 'terminal.accessible-buffer'; - private _xterm: IXtermTerminal & { raw: Terminal } | undefined; - static get(instance: ITerminalInstance): AccessibleBufferContribution | null { - return instance.getContribution(AccessibleBufferContribution.ID); - } - private _accessibleBufferWidget: AccessibleBufferWidget | undefined; +export class TerminalAccessibleViewContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.accessibleBufferProvider'; + static get(instance: ITerminalInstance): TerminalAccessibleViewContribution | null { + return instance.getContribution(TerminalAccessibleViewContribution.ID); + } + private _bufferTracker: BufferContentTracker | undefined; + private _xterm: Pick & { raw: Terminal } | undefined; constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService - ) { + @ITerminalService private readonly _terminalService: ITerminalService) { super(); - this.add(_instance.onDidRunText(() => { - const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); - if (focusAfterRun === 'terminal') { - _instance.focus(true); - } else if (focusAfterRun === 'accessible-buffer') { - this.show(); + this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { + if (this._terminalService.activeInstance !== this._instance) { + return false; } - })); + this.show(); + return true; + }, TerminalContextKeys.focus)); } - layout(xterm: IXtermTerminal & { raw: Terminal }): void { + xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { + const addon = this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities); + xterm.raw.loadAddon(addon); + addon.activate(xterm.raw); this._xterm = xterm; } - async show(): Promise { + show(): void { if (!this._xterm) { return; } - if (!this._accessibleBufferWidget) { - this._accessibleBufferWidget = this.add(this._instantiationService.createInstance(AccessibleBufferWidget, this._instance, this._xterm)); + if (!this._bufferTracker) { + this._bufferTracker = this._register(this._instantiationService.createInstance(BufferContentTracker, this._xterm)); } - await this._accessibleBufferWidget.show(); + this._accessibleViewService.show(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + // wait for the render to happen so that the line count is correct and + // the cursor is at the bottom of the buffer + setTimeout(() => { + const lastPosition = this._accessibleViewService.getLastPosition(); + if (lastPosition) { + this._accessibleViewService.setPosition(lastPosition, true); + } + }, 50); } - - async createCommandQuickPick(): Promise | undefined> { - return this._accessibleBufferWidget?.createQuickPick(); - } - navigateToCommand(type: NavigationType): void { - return this._accessibleBufferWidget?.navigateToCommand(type); + const currentLine = this._accessibleViewService.getPosition()?.lineNumber || this._accessibleViewService.getLastPosition()?.lineNumber; + const commands = this._getCommandsWithEditorLine(); + if (!commands?.length || !currentLine) { + return; + } + + const filteredCommands = type === NavigationType.Previous ? commands.filter(c => c.lineNumber < currentLine).sort((a, b) => b.lineNumber - a.lineNumber) : commands.filter(c => c.lineNumber > currentLine).sort((a, b) => a.lineNumber - b.lineNumber); + if (!filteredCommands.length) { + return; + } + this._accessibleViewService.setPosition(new Position(filteredCommands[0].lineNumber, 1), true); } - hide(): void { - this._accessibleBufferWidget?.hide(); + + private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = capability?.commands; + const currentCommand = capability?.currentCommand; + if (!commands?.length) { + return; + } + const result: ICommandWithEditorLine[] = []; + for (const command of commands) { + const lineNumber = this._getEditorLineForCommand(command); + if (!lineNumber) { + continue; + } + result.push({ command, lineNumber }); + } + if (currentCommand) { + const lineNumber = this._getEditorLineForCommand(currentCommand); + if (!!lineNumber) { + result.push({ command: currentCommand, lineNumber }); + } + } + return result; } + + private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { + if (!this._bufferTracker) { + return; + } + let line: number | undefined; + if ('marker' in command) { + line = command.marker?.line; + } else if ('commandStartMarker' in command) { + line = command.commandStartMarker?.line; + } + if (line === undefined || line < 0) { + return; + } + line = this._bufferTracker.bufferToEditorLineMapping.get(line); + if (line === undefined) { + return; + } + return line + 1; + } + } -registerTerminalContribution(AccessibleBufferContribution.ID, AccessibleBufferContribution); +registerTerminalContribution(TerminalAccessibleViewContribution.ID, TerminalAccessibleViewContribution); export class TerminalAccessibilityHelpContribution extends Disposable { static ID: 'terminalAccessibilityHelpContribution'; @@ -115,72 +174,51 @@ export class TerminalAccessibilityHelpContribution extends Disposable { return; } accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleContentProvider, instance, terminal)); - }, ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus))); + }, ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))))); } } registerTerminalContribution(TerminalAccessibilityHelpContribution.ID, TerminalAccessibilityHelpContribution); -registerTerminalAction({ - id: TerminalCommandId.FocusAccessibleBuffer, - title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - keybinding: [ - { - primary: KeyMod.Alt | KeyCode.F2, - secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], - linux: { - primary: KeyMod.Alt | KeyCode.F2 | KeyMod.Shift, - secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] - }, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus, ContextKeyExpr.or(terminalTabFocusModeContextKey, TerminalContextKeys.accessibleBufferFocus.negate())) - } - ], - run: async (c) => { - const instance = await c.service.getActiveOrCreateInstance(); - await c.service.revealActiveTerminal(); - if (!instance) { - return; - } - await AccessibleBufferContribution.get(instance)?.show(); - } -}); -registerTerminalAction({ - id: TerminalCommandId.NavigateAccessibleBuffer, - title: { value: localize('workbench.action.terminal.navigateAccessibleBuffer', 'Navigate Accessible Buffer'), original: 'Navigate Accessible Buffer' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, - weight: KeybindingWeight.WorkbenchContrib + 2, - when: TerminalContextKeys.accessibleBufferFocus - } - ], - run: async (c) => { - const instance = await c.service.getActiveOrCreateInstance(); - await c.service.revealActiveTerminal(); - if (!instance) { +class FocusAccessibleBufferAction extends Action2 { + constructor() { + super({ + id: TerminalCommandId.FocusAccessibleBuffer, + title: { value: localize('workbench.action.terminal.focusAccessibleBuffer', 'Focus Accessible Buffer'), original: 'Focus Accessible Buffer' }, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), + keybinding: [ + { + primary: KeyMod.Alt | KeyCode.F2, + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow], + linux: { + primary: KeyMod.Alt | KeyCode.F2 | KeyMod.Shift, + secondary: [KeyMod.CtrlCmd | KeyCode.UpArrow] + }, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, TerminalContextKeys.focus) + } + ] + }); + } + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const terminalService = accessor.get(ITerminalService); + const terminal = await terminalService.getActiveOrCreateInstance(); + if (!terminal?.xterm) { return; } - const quickPick = await AccessibleBufferContribution.get(instance)?.createCommandQuickPick(); - quickPick?.show(); + TerminalAccessibleViewContribution.get(terminal)?.show(); } -}); +} +registerAction2(FocusAccessibleBufferAction); registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToNextCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToNextCommand', 'Accessible Buffer Go to Next Command'), original: 'Accessible Buffer Go to Next Command' }, - precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.DownArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -190,7 +228,7 @@ registerTerminalAction({ if (!instance) { return; } - await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Next); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Next); } }); @@ -198,16 +236,11 @@ registerTerminalAction({ registerTerminalAction({ id: TerminalCommandId.AccessibleBufferGoToPreviousCommand, title: { value: localize('workbench.action.terminal.accessibleBufferGoToPreviousCommand', 'Accessible Buffer Go to Previous Command'), original: 'Accessible Buffer Go to Previous Command' }, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.accessibleBufferFocus), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), keybinding: [ - { - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - weight: KeybindingWeight.WorkbenchContrib + 2 - }, { primary: KeyMod.Alt | KeyCode.UpArrow, - when: ContextKeyExpr.and(TerminalContextKeys.accessibleBufferFocus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))), weight: KeybindingWeight.WorkbenchContrib + 2 } ], @@ -217,6 +250,6 @@ registerTerminalAction({ if (!instance) { return; } - await AccessibleBufferContribution.get(instance)?.navigateToCommand(NavigationType.Previous); + await TerminalAccessibleViewContribution.get(instance)?.navigateToCommand(NavigationType.Previous); } }); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index 340427fd528..ec61041471b 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -8,15 +8,15 @@ import { format } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import type { Terminal } from 'xterm'; export const enum ClassName { @@ -29,7 +29,8 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc private readonly _hasShellIntegration: boolean = false; onClose() { - if (this._contextKeyService.getContextKeyValue(TerminalContextKeys.accessibleBufferFocus.key) === true) { + const expr = ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)); + if (expr?.evaluate(this._contextKeyService.getContext(null))) { this._commandService.executeCommand(TerminalCommandId.FocusAccessibleBuffer); } else { this._instance.focus(); @@ -84,7 +85,7 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc shellIntegrationCommandList.push(localize('shellIntegration', "The terminal has a feature called shell integration that offers an enhanced experience and provides useful commands for screen readers such as:")); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToNextCommand, localize('goToNextCommand', 'Go to Next Command ({0})'), localize('goToNextCommandNoKb', 'Go to Next Command is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToPreviousCommand, localize('goToPreviousCommand', 'Go to Previous Command ({0})'), localize('goToPreviousCommandNoKb', 'Go to Previous Command is currently not triggerable by a keybinding.'))); - shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.NavigateAccessibleBuffer, localize('navigateAccessibleBuffer', 'Navigate Accessible Buffer ({0})'), localize('navigateAccessibleBufferNoKb', 'Navigate Accessible Buffer is currently not triggerable by a keybinding.'))); + shellIntegrationCommandList.push('- ' + this._descriptionForCommand(AccessibilityCommandId.GoToSymbol, localize('goToSymbol', 'Go to Symbol ({0})'), localize('goToSymbolNoKb', 'Go to symbol is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.RunRecentCommand, localize('runRecentCommand', 'Run Recent Command ({0})'), localize('runRecentCommandNoKb', 'Run Recent Command is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.GoToRecentDirectory, localize('goToRecentDirectory', 'Go to Recent Directory ({0})'), localize('goToRecentDirectoryNoKb', 'Go to Recent Directory is currently not triggerable by a keybinding.'))); content.push(shellIntegrationCommandList.join('\n')); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts deleted file mode 100644 index 8e6c44210bb..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBuffer.ts +++ /dev/null @@ -1,268 +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 { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { localize } from 'vs/nls'; -import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; -import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; -import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; -import { TerminalAccessibleWidget } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget'; -import type { Terminal } from 'xterm'; - -export const enum NavigationType { - Next = 'next', - Previous = 'previous' -} - -interface IAccessibleBufferQuickPickItem extends IQuickPickItem { - lineNumber: number; - exitCode?: number; -} - -export const enum ClassName { - AccessibleBuffer = 'accessible-buffer', - Active = 'active' -} - -export class AccessibleBufferWidget extends TerminalAccessibleWidget { - private _isUpdating: boolean = false; - private _pendingUpdates = 0; - - private _bufferTracker: BufferContentTracker; - - private _cursorPosition: { lineNumber: number; column: number } | undefined; - - constructor( - _instance: Pick, - _xterm: Pick & { raw: Terminal }, - @IInstantiationService _instantiationService: IInstantiationService, - @IModelService _modelService: IModelService, - @IConfigurationService _configurationService: IConfigurationService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @IAudioCueService private readonly _audioCueService: IAudioCueService, - @IContextKeyService _contextKeyService: IContextKeyService, - @ITerminalLogService private readonly _logService: ITerminalLogService, - @ITerminalService _terminalService: ITerminalService - ) { - super(ClassName.AccessibleBuffer, _instance, _xterm, TerminalContextKeys.accessibleBufferFocus, TerminalContextKeys.accessibleBufferOnLastLine, _instantiationService, _modelService, _configurationService, _contextKeyService, _terminalService); - this._bufferTracker = _instantiationService.createInstance(BufferContentTracker, _xterm); - this.element.ariaRoleDescription = localize('terminal.integrated.accessibleBuffer', 'Terminal buffer'); - _instance.onDidRequestFocus(() => this.hide(true)); - this.updateEditor(); - // xterm's initial layout call has already happened - this.layout(); - } - - navigateToCommand(type: NavigationType): void { - const currentLine = this.editorWidget.getPosition()?.lineNumber || this._getDefaultCursorPosition()?.lineNumber; - const commands = this._getCommandsWithEditorLine(); - if (!commands?.length || !currentLine) { - return; - } - - const filteredCommands = type === NavigationType.Previous ? commands.filter(c => c.lineNumber < currentLine).sort((a, b) => b.lineNumber - a.lineNumber) : commands.filter(c => c.lineNumber > currentLine).sort((a, b) => a.lineNumber - b.lineNumber); - if (!filteredCommands.length) { - return; - } - this._cursorPosition = { lineNumber: filteredCommands[0].lineNumber, column: 1 }; - this._resetPosition(); - } - - private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { - let line: number | undefined; - if ('marker' in command) { - line = command.marker?.line; - } else if ('commandStartMarker' in command) { - line = command.commandStartMarker?.line; - } - if (line === undefined || line < 0) { - return; - } - line = this._bufferTracker.bufferToEditorLineMapping.get(line); - if (line === undefined) { - return; - } - return line + 1; - } - - private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { - const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); - const commands = capability?.commands; - const currentCommand = capability?.currentCommand; - if (!commands?.length) { - return; - } - const result: ICommandWithEditorLine[] = []; - for (const command of commands) { - const lineNumber = this._getEditorLineForCommand(command); - if (!lineNumber) { - continue; - } - result.push({ command, lineNumber }); - } - if (currentCommand) { - const lineNumber = this._getEditorLineForCommand(currentCommand); - if (!!lineNumber) { - result.push({ command: currentCommand, lineNumber }); - } - } - return result; - } - - async createQuickPick(): Promise | undefined> { - this._cursorPosition = this.editorWidget.getPosition() ?? undefined; - const commands = this._getCommandsWithEditorLine(); - if (!commands) { - return; - } - const quickPickItems: IAccessibleBufferQuickPickItem[] = []; - for (const { command, lineNumber } of commands) { - const line = this._getEditorLineForCommand(command); - if (!line) { - continue; - } - quickPickItems.push( - { - label: localize('terminal.integrated.symbolQuickPick.labelNoExitCode', '{0}', command.command), - lineNumber, - exitCode: 'exitCode' in command ? command.exitCode : undefined - }); - } - const quickPick = this._quickInputService.createQuickPick(); - quickPick.canSelectMany = false; - quickPick.onDidChangeActive(() => { - const activeItem = quickPick.activeItems[0]; - if (!activeItem) { - return; - } - if (activeItem.exitCode) { - this._audioCueService.playAudioCue(AudioCue.error, { allowManyInParallel: true, source: 'accessibleBufferWidget' }); - } - this.editorWidget.revealLine(activeItem.lineNumber, 0); - }); - quickPick.onDidHide(() => { - this._resetPosition(); - quickPick.dispose(); - }); - quickPick.onDidAccept(() => { - const item = quickPick.activeItems[0]; - const model = this.editorWidget.getModel(); - if (!model) { - return; - } - if (!item && this._cursorPosition) { - this._resetPosition(); - } else { - this._cursorPosition = { lineNumber: item.lineNumber, column: 1 }; - } - quickPick.dispose(); - this.editorWidget.focus(); - return; - }); - quickPick.items = quickPickItems.reverse(); - return quickPick; - } - - private _resetPosition(): void { - this._cursorPosition = this._cursorPosition ?? this._getDefaultCursorPosition(); - if (!this._cursorPosition) { - return; - } - this.editorWidget.setPosition(this._cursorPosition); - this.editorWidget.setScrollPosition({ scrollTop: this.editorWidget.getTopForLineNumber(this._cursorPosition.lineNumber) }); - } - - override layout(): void { - if (this._bufferTracker) { - this._bufferTracker.reset(); - } - super.layout(); - } - - async updateEditor(dataChanged?: boolean): Promise { - if (this._isUpdating) { - this._pendingUpdates++; - return; - } - this._isUpdating = true; - const model = await this._updateModel(dataChanged); - if (!model) { - return; - } - this._isUpdating = false; - if (this._pendingUpdates) { - this._logService.debug('TerminalAccessibleBuffer._updateEditor: pending updates', this._pendingUpdates); - this._pendingUpdates--; - await this.updateEditor(dataChanged); - } - } - - override registerListeners(): void { - super.registerListeners(); - this._xterm.raw.onWriteParsed(async () => { - if (this._xterm.raw.buffer.active.baseY === 0) { - await this.updateEditor(true); - } - }); - const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); - this._listeners.push(onRequestUpdateEditor(async () => await this.updateEditor(true))); - } - - private _getDefaultCursorPosition(): { lineNumber: number; column: number } | undefined { - const modelLineCount = this.editorWidget.getModel()?.getLineCount(); - return modelLineCount ? { lineNumber: modelLineCount, column: 1 } : undefined; - } - - private async _updateModel(dataChanged?: boolean): Promise { - const linesBefore = this._bufferTracker.lines.length; - this._bufferTracker.update(); - const linesAfter = this._bufferTracker.lines.length; - const modelChanged = linesBefore !== linesAfter; - - // Save the view state before the update if it was set by the user - let savedViewState: IEditorViewState | undefined; - if (dataChanged) { - savedViewState = this.editorWidget.saveViewState() ?? undefined; - } - - let model = this.editorWidget.getModel(); - const text = this._bufferTracker.lines.join('\n'); - if (model) { - model.setValue(text); - } else { - model = await this.getTextModel(this._instance.resource.with({ fragment: `${ClassName.AccessibleBuffer}-${text}` })); - } - this.editorWidget.setModel(model); - - // If the model changed due to new data, restore the view state - // If the model changed due to a refresh or the cursor is a the top, set to the bottom of the buffer - // Otherwise, don't change the position - const positionTopOfBuffer = this.editorWidget.getPosition()?.lineNumber === 1 && this.editorWidget.getPosition()?.column === 1; - if (savedViewState) { - this.editorWidget.restoreViewState(savedViewState); - } else if (modelChanged || positionTopOfBuffer) { - const defaultPosition = this._getDefaultCursorPosition(); - if (defaultPosition) { - this.editorWidget.setPosition(defaultPosition); - this.editorWidget.setScrollPosition({ scrollTop: this.editorWidget.getTopForLineNumber(defaultPosition.lineNumber) }); - } - } - return model!; - } -} - -interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } - diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts new file mode 100644 index 00000000000..c4ba54d12a6 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { IXtermTerminal, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; +import type { Terminal } from 'xterm'; +import { Event } from 'vs/base/common/event'; + +export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { + options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal' }; + verbositySettingKey = AccessibilityVerbositySettingId.Terminal; + private _xterm: IXtermTerminal & { raw: Terminal } | undefined; + constructor( + private readonly _instance: Pick, + private _bufferTracker: BufferContentTracker, + @IModelService _modelService: IModelService, + @IConfigurationService _configurationService: IConfigurationService, + @IContextKeyService _contextKeyService: IContextKeyService, + @ITerminalService _terminalService: ITerminalService, + @IConfigurationService configurationService: IConfigurationService, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + ) { + super(); + this.add(_instance.onDidRunText(() => { + const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); + if (focusAfterRun === 'terminal') { + _instance.focus(true); + } else if (focusAfterRun === 'accessible-buffer') { + _accessibleViewService.show(this); + } + })); + this.registerListeners(); + } + + onClose() { + this._instance.focus(); + } + registerListeners(): void { + if (!this._xterm) { + return; + } + this._xterm.raw.onWriteParsed(async () => { + if (this._xterm!.raw.buffer.active.baseY === 0) { + this._bufferTracker.update(); + this._accessibleViewService.show(this); + } + }); + const onRequestUpdateEditor = Event.latch(this._xterm.raw.onScroll); + this.add(onRequestUpdateEditor(() => this._accessibleViewService.show(this))); + } + + provideContent(): string { + this._bufferTracker.update(); + return this._bufferTracker.lines.join('\n'); + } + + getSymbols(): IAccessibleViewSymbol[] { + const commands = this._getCommandsWithEditorLine() ?? []; + const symbols: IAccessibleViewSymbol[] = []; + for (const command of commands) { + const label = command.command.command; + if (label) { + symbols.push({ + label, + lineNumber: command.lineNumber + }); + } + } + return symbols; + } + + private _getCommandsWithEditorLine(): ICommandWithEditorLine[] | undefined { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + const commands = capability?.commands; + const currentCommand = capability?.currentCommand; + if (!commands?.length) { + return; + } + const result: ICommandWithEditorLine[] = []; + for (const command of commands) { + const lineNumber = this._getEditorLineForCommand(command); + if (lineNumber === undefined) { + continue; + } + result.push({ command, lineNumber }); + } + if (currentCommand) { + const lineNumber = this._getEditorLineForCommand(currentCommand); + if (lineNumber !== undefined) { + result.push({ command: currentCommand, lineNumber }); + } + } + return result; + } + private _getEditorLineForCommand(command: ITerminalCommand | ICurrentPartialCommand): number | undefined { + let line: number | undefined; + if ('marker' in command) { + line = command.marker?.line; + } else if ('commandStartMarker' in command) { + line = command.commandStartMarker?.line; + } + if (line === undefined || line < 0) { + return; + } + line = this._bufferTracker.bufferToEditorLineMapping.get(line); + if (line === undefined) { + return; + } + return line + 1; + } +} +export interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts deleted file mode 100644 index 448cd3da0e4..00000000000 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ /dev/null @@ -1,177 +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 { KeyCode } from 'vs/base/common/keyCodes'; -import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import * as dom from 'vs/base/browser/dom'; -import { Event } from 'vs/base/common/event'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import type { Terminal } from 'xterm'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; -import { localize } from 'vs/nls'; - -const enum ClassName { - Active = 'active', - Hide = 'hide', - Widget = 'terminal-accessible-widget' -} - -export abstract class TerminalAccessibleWidget extends DisposableStore { - - private _element: HTMLElement; - get element(): HTMLElement { return this._element; } - private _editorWidget: CodeEditorWidget; - protected get editorWidget(): CodeEditorWidget { return this._editorWidget; } - private _editorContainer: HTMLElement; - private _xtermElement: HTMLElement; - - protected _listeners: IDisposable[] = []; - - private readonly _focusedContextKey: IContextKey; - private readonly _focusedLastLineContextKey: IContextKey; - private readonly _focusTracker?: dom.IFocusTracker; - - constructor( - private readonly _className: string, - protected readonly _instance: Pick, - protected readonly _xterm: Pick & { raw: Terminal }, - rawFocusContextKey: RawContextKey, - rawFocusLastLineContextKey: RawContextKey, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IModelService private readonly _modelService: IModelService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IContextKeyService protected readonly _contextKeyService: IContextKeyService, - @ITerminalService private readonly _terminalService: ITerminalService - ) { - super(); - this._xtermElement = _xterm.raw.element!; - this._element = document.createElement('div'); - this._element.setAttribute('role', 'document'); - this._element.classList.add(_className); - this._element.classList.add(ClassName.Widget); - this._editorContainer = document.createElement('div'); - const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) - }; - const font = _xterm.getFont(); - const editorOptions: IEditorConstructionOptions = { - ...getSimpleEditorOptions(this._configurationService), - lineDecorationsWidth: 6, - dragAndDrop: true, - cursorWidth: 1, - fontSize: font.fontSize, - lineHeight: font.charHeight ? font.charHeight * font.lineHeight : 1, - letterSpacing: font.letterSpacing, - fontFamily: font.fontFamily, - wrappingStrategy: 'advanced', - wrappingIndent: 'none', - padding: { top: 2, bottom: 2 }, - quickSuggestions: false, - renderWhitespace: 'none', - dropIntoEditor: { enabled: true }, - readOnly: true, - ariaLabel: localize('terminalAccessibleBuffer', "Terminal Buffer") - }; - this._editorWidget = this.add(this._instantiationService.createInstance(CodeEditorWidget, this._editorContainer, editorOptions, codeEditorWidgetOptions)); - this._element.replaceChildren(this._editorContainer); - this._xtermElement.insertAdjacentElement('beforebegin', this._element); - - this._focusTracker = this.add(dom.trackFocus(this._editorContainer)); - this._focusedContextKey = rawFocusContextKey.bindTo(this._contextKeyService); - this._focusedLastLineContextKey = rawFocusLastLineContextKey.bindTo(this._contextKeyService); - this.add(this._focusTracker.onDidFocus(() => { - this._focusedContextKey?.set(true); - this._focusedLastLineContextKey?.set(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - })); - this.add(this._focusTracker.onDidBlur(() => { - this._focusedContextKey?.reset(); - this._focusedLastLineContextKey?.reset(); - })); - this._editorWidget.onDidChangeCursorPosition(() => { - console.log(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - this._focusedLastLineContextKey?.set(this._editorWidget.getSelection()?.positionLineNumber === this._editorWidget.getModel()?.getLineCount()); - }); - - this.add(Event.runAndSubscribe(this._xterm.raw.onResize, () => this.layout())); - this.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectedKeys.has(TerminalSettingId.FontFamily) || e.affectedKeys.has(TerminalSettingId.FontSize) || e.affectedKeys.has(TerminalSettingId.LineHeight) || e.affectedKeys.has(TerminalSettingId.LetterSpacing)) { - const font = this._xterm.getFont(); - this._editorWidget.updateOptions({ fontFamily: font.fontFamily, fontSize: font.fontSize, lineHeight: font.charHeight ? font.charHeight * font.lineHeight : 1, letterSpacing: font.letterSpacing }); - } - })); - this.add(this._editorWidget.onKeyDown((e) => { - switch (e.keyCode) { - case KeyCode.Escape: - // On escape, hide the accessible buffer and force focus onto the terminal - this.hide(true); - break; - } - })); - this.add(this._editorWidget.onDidFocusEditorText(async () => { - this._terminalService.setActiveInstance(this._instance as ITerminalInstance); - this._xtermElement.classList.add(ClassName.Hide); - })); - this.add(this._editorWidget.onDidBlurEditorText(async () => this.hide())); - } - - registerListeners(): void { - this._listeners.push(this._instance.onDidRequestFocus(() => this.editorWidget.focus())); - } - - layout(): void { - this._editorWidget.layout({ width: this._xtermElement.clientWidth, height: this._xtermElement.clientHeight }); - } - - abstract updateEditor(): Promise; - - async show(): Promise { - this.registerListeners(); - await this.updateEditor(); - this.element.tabIndex = -1; - this.layout(); - this.element.classList.add(ClassName.Active); - this._xtermElement.classList.add(ClassName.Hide); - this.editorWidget.focus(); - } - - override dispose(): void { - this._disposeListeners(); - super.dispose(); - } - - private _disposeListeners(): void { - for (const listener of this._listeners) { - listener.dispose(); - } - } - - hide(focusXterm?: boolean): void { - this._disposeListeners(); - this.element.classList.remove(ClassName.Active); - this._xtermElement.classList.remove(ClassName.Hide); - if (focusXterm) { - this._xterm.raw.focus(); - } - } - - async getTextModel(resource: URI): Promise { - const existing = this._modelService.getModel(resource); - if (existing && !existing.isDisposed()) { - return existing; - } - return this._modelService.createModel(`${this._className}-${resource.fragment}`, null, resource, false); - } -} diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index b250ab72630..5f2a3678c61 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -64,7 +64,7 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(ILoggerService, store.add(new TestLoggerService())); instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); - instantiationService.stub(IContextKeyService, new MockContextKeyService()); + instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); capabilities = store.add(new TerminalCapabilityStore()); if (!isWindows) { @@ -75,7 +75,7 @@ suite('Buffer Content Tracker', () => { const container = document.createElement('div'); xterm.raw.open(container); configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${cwd}', description: '${cwd}' } } } }); - bufferTracker = instantiationService.createInstance(BufferContentTracker, xterm); + bufferTracker = store.add(instantiationService.createInstance(BufferContentTracker, xterm)); }); test('should not clear the prompt line', async () => { diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index 5d144353e9f..51bd07850d8 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -10,6 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; @@ -113,11 +114,16 @@ registerActiveInstanceAction({ f1: true, category, precondition: TerminalContextKeys.terminalHasBeenCreated, - keybinding: { + keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 1, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus) + when: TerminalContextKeys.focus + }, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, + weight: KeybindingWeight.WorkbenchContrib + 1, + when: ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)) }, + ], run: (activeInstance) => TerminalLinkContribution.get(activeInstance)?.showLinkQuickpick() }); registerActiveInstanceAction({ diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index 77b8a2fd3c1..01d1919eecf 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -66,6 +66,7 @@ .test-output-peek-tree { color: var(--vscode-editor-foreground); + border-left: 1px solid var(--vscode-panelSection-border); } .test-output-peek-tree .monaco-list-row .monaco-action-bar, diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 0b42f25d628..ade1e1091e3 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -68,6 +68,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; +import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -1492,6 +1493,7 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { terminal.xterm.write(chunk.buffer, () => pendingWrites.value--); } } else { + didWriteData = true; this.writeNotice(terminal, localize('runNoOutputForPast', 'Test output is only available for new test runs.')); } @@ -1527,7 +1529,7 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { } private writeNotice(terminal: IDetachedTerminalInstance, str: string) { - terminal.xterm.write(`\x1b[2m${str}\x1b[0m`); + terminal.xterm.write(formatMessageForTerminal(str)); } private attachTerminalToDom(terminal: IDetachedTerminalInstance) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index e037a4298f8..1549fc0631e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -19,7 +19,7 @@ import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/acti import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, ContextKeyTrueExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { QuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -54,6 +54,11 @@ import { ctxIsMergeResultEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/me import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IFileService } from 'vs/platform/files/common/files'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; type ConfigureSyncQuickPickItem = { id: SyncResource; label: string; description?: string }; @@ -712,6 +717,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerShowLogAction(); this.registerResetSyncDataAction(); this.registerAcceptMergesAction(); + this.registerDownloadSyncActivityAction(); } private registerTurnOnSyncAction(): void { @@ -1125,6 +1131,65 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo })); } + private registerDownloadSyncActivityAction(): void { + this._register(registerAction2(class DownloadSyncActivityAction extends Action2 { + constructor() { + super({ + id: 'workbench.userDataSync.actions.downloadSyncActivity', + title: { original: 'Download Settings Sync Activity', value: localize('download sync activity title', "Download Settings Sync Activity") }, + category: Categories.Developer, + f1: true, + precondition: ContextKeyExpr.and(CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) + }); + } + + async run(accessor: ServicesAccessor): Promise { + const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService); + const fileDialogService = accessor.get(IFileDialogService); + const progressService = accessor.get(IProgressService); + const uriIdentityService = accessor.get(IUriIdentityService); + const fileService = accessor.get(IFileService); + const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); + + const result = await fileDialogService.showOpenDialog({ + title: localize('download sync activity dialog title', "Select folder to download Settings Sync activity"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: localize('download sync activity dialog open label', "Save"), + }); + + if (!result?.[0]) { + return; + } + + await progressService.withProgress({ location: ProgressLocation.Window }, async () => { + const machines = await userDataSyncMachinesService.getMachines(); + const currentMachine = machines.find(m => m.isCurrent); + const name = (currentMachine ? currentMachine.name + ' - ' : '') + 'Settings Sync Activity'; + const stat = await fileService.resolve(result[0]); + + const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); + const indexes: number[] = []; + for (const child of stat.children ?? []) { + if (child.name === name) { + indexes.push(0); + } else { + const matches = nameRegEx.exec(child.name); + if (matches) { + indexes.push(parseInt(matches[1])); + } + } + } + indexes.sort((a, b) => a - b); + + return userDataSyncWorkbenchService.downloadSyncActivity(uriIdentityService.extUri.joinPath(result[0], indexes[0] !== 0 ? name : `${name} ${indexes[indexes.length - 1] + 1}`)); + }); + } + + })); + } + private registerViews(): void { const container = this.registerViewContainer(); this.registerDataViews(container); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 2c294a869c6..e3ae0c168a9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -9,14 +9,14 @@ import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ALL_SYNC_RESOURCES, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, getLastSyncResourceUri, SyncResource, ISyncUserDataProfile, USER_DATA_SYNC_LOG_ID } from 'vs/platform/userDataSync/common/userDataSync'; +import { ALL_SYNC_RESOURCES, IUserDataSyncService, ISyncResourceHandle as IResourceHandle, SyncStatus, IUserDataSyncEnablementService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, getLastSyncResourceUri, SyncResource, ISyncUserDataProfile, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI, UriDto } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { FolderThemeIcon } from 'vs/platform/theme/common/themeService'; import { fromNow } from 'vs/base/common/date'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Codicon } from 'vs/base/common/codicons'; @@ -55,6 +55,7 @@ export class UserDataSyncDataViews extends Disposable { this.registerActivityView(container, false); this.registerTroubleShootView(container); + this.registerExternalActivityView(container); } private registerConflictsView(container: ViewContainer): void { @@ -167,6 +168,58 @@ export class UserDataSyncDataViews extends Disposable { this.registerDataViewActions(id); } + private registerExternalActivityView(container: ViewContainer): void { + const id = `workbench.views.sync.externalActivity`; + const name = localize('downloaded sync activity title', "Sync Activity (Developer)"); + const dataProvider = this.instantiationService.createInstance(ExtractedUserDataSyncActivityViewDataProvider, undefined); + const treeView = this.instantiationService.createInstance(TreeView, id, name); + treeView.showCollapseAllAction = false; + treeView.showRefreshAction = false; + treeView.dataProvider = dataProvider; + + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViews([{ + id, + name, + ctorDescriptor: new SyncDescriptor(TreeViewPane), + when: CONTEXT_ENABLE_ACTIVITY_VIEWS, + canToggleVisibility: true, + canMoveView: false, + treeView, + collapsed: false, + hideByDefault: false, + }], container); + + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.sync.extractActivity`, + title: localize('workbench.actions.sync.extractActivity', "Extract Sync Activity"), + icon: Codicon.cloudUpload, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', id), + group: 'navigation', + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + const fileDialogService = accessor.get(IFileDialogService); + const result = await fileDialogService.showOpenDialog({ + title: localize('select sync activity file', "Select Sync Activity File or Folder"), + canSelectFiles: true, + canSelectFolders: true, + canSelectMany: false, + }); + if (!result?.[0]) { + return; + } + dataProvider.activityDataResource = result[0]; + await treeView.refresh(); + } + })); + } + private registerDataViewActions(viewId: string) { registerAction2(class extends Action2 { constructor() { @@ -289,6 +342,7 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre constructor( @IUserDataSyncService protected readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService protected readonly userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, @IUserDataAutoSyncService protected readonly userDataAutoSyncService: IUserDataAutoSyncService, @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService private readonly notificationService: INotificationService, @@ -365,8 +419,8 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre protected async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { const syncResourceHandle = (element).syncResourceHandle; - const associatedResources = await this.userDataSyncService.getAssociatedResources(syncResourceHandle); - const previousAssociatedResources = syncResourceHandle.previous ? await this.userDataSyncService.getAssociatedResources(syncResourceHandle.previous) : []; + const associatedResources = await this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle); + const previousAssociatedResources = syncResourceHandle.previous ? await this.userDataSyncResourceProviderService.getAssociatedResources(syncResourceHandle.previous) : []; return associatedResources.map(({ resource, comparableResource }) => { const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource.toString() }); const previousResource = previousAssociatedResources.find(previous => basename(previous.resource) === basename(resource))?.resource; @@ -419,14 +473,20 @@ abstract class UserDataSyncActivityViewDataProvider implements ITre protected abstract getResourceHandles(syncResource: SyncResource, profile?: T): Promise; } -class LocalUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { +class LocalUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { - protected getResourceHandles(syncResource: SyncResource, profile: IUserDataProfile | undefined): Promise { - return this.userDataSyncService.getLocalSyncResourceHandles(syncResource, profile); + protected getResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile); } - protected async getProfiles(): Promise { - return this.userDataProfilesService.profiles.filter(p => !p.isDefault); + protected async getProfiles(): Promise { + return this.userDataProfilesService.profiles + .filter(p => !p.isDefault) + .map(p => ({ + id: p.id, + collection: p.id, + name: p.name, + })); } } @@ -436,13 +496,14 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie constructor( @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService, @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @INotificationService notificationService: INotificationService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, ) { - super(userDataSyncService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); + super(userDataSyncService, userDataSyncResourceProviderService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); } override async getChildren(element?: ITreeItem): Promise { @@ -460,17 +521,17 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie } protected getResourceHandles(syncResource: SyncResource, profile?: ISyncUserDataProfile): Promise { - return this.userDataSyncService.getRemoteSyncResourceHandles(syncResource, profile); + return this.userDataSyncResourceProviderService.getRemoteSyncResourceHandles(syncResource, profile); } protected getProfiles(): Promise { - return this.userDataSyncService.getRemoteProfiles(); + return this.userDataSyncResourceProviderService.getRemoteSyncedProfiles(); } protected override async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { const children = await super.getChildrenForSyncResourceTreeItem(element); if (children.length) { - const machineId = await this.userDataSyncService.getMachineId(element.syncResourceHandle); + const machineId = await this.userDataSyncResourceProviderService.getMachineId(element.syncResourceHandle); if (machineId) { const machines = await this.getMachines(); const machine = machines.find(({ id }) => id === machineId); @@ -481,6 +542,73 @@ class RemoteUserDataSyncActivityViewDataProvider extends UserDataSyncActivityVie } } +class ExtractedUserDataSyncActivityViewDataProvider extends UserDataSyncActivityViewDataProvider { + + private machinesPromise: Promise | undefined; + + private activityDataLocation: URI | undefined; + + constructor( + public activityDataResource: URI | undefined, + @IUserDataSyncService userDataSyncService: IUserDataSyncService, + @IUserDataSyncResourceProviderService userDataSyncResourceProviderService: IUserDataSyncResourceProviderService, + @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, + @IUserDataSyncWorkbenchService userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, + @INotificationService notificationService: INotificationService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + super(userDataSyncService, userDataSyncResourceProviderService, userDataAutoSyncService, userDataSyncWorkbenchService, notificationService, userDataProfilesService); + } + + override async getChildren(element?: ITreeItem): Promise { + if (!element) { + this.machinesPromise = undefined; + if (!this.activityDataResource) { + return []; + } + const stat = await this.fileService.resolve(this.activityDataResource); + if (stat.isDirectory) { + this.activityDataLocation = this.activityDataResource; + } else { + this.activityDataLocation = this.uriIdentityService.extUri.joinPath(this.uriIdentityService.extUri.dirname(this.activityDataResource), 'remoteActivity'); + try { await this.fileService.del(this.activityDataLocation, { recursive: true }); } catch (e) {/* ignore */ } + await this.userDataSyncService.extractActivityData(this.activityDataResource, this.activityDataLocation); + } + } + return super.getChildren(element); + } + + protected getResourceHandles(syncResource: SyncResource, profile: ISyncUserDataProfile | undefined): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncResourceHandles(syncResource, profile, this.activityDataLocation); + } + + protected override async getProfiles(): Promise { + return this.userDataSyncResourceProviderService.getLocalSyncedProfiles(this.activityDataLocation); + } + + protected override async getChildrenForSyncResourceTreeItem(element: SyncResourceHandleTreeItem): Promise { + const children = await super.getChildrenForSyncResourceTreeItem(element); + if (children.length) { + const machineId = await this.userDataSyncResourceProviderService.getMachineId(element.syncResourceHandle); + if (machineId) { + const machines = await this.getMachines(); + const machine = machines.find(({ id }) => id === machineId); + children[0].description = machine?.isCurrent ? localize({ key: 'current', comment: ['Represents current machine'] }, "Current") : machine?.name; + } + } + return children; + } + + private getMachines(): Promise { + if (this.machinesPromise === undefined) { + this.machinesPromise = this.userDataSyncResourceProviderService.getLocalSyncedMachines(this.activityDataLocation); + } + return this.machinesPromise; + } +} + class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider { private machinesPromise: Promise | undefined; @@ -599,6 +727,7 @@ class UserDataSyncTroubleshootViewDataProvider implements ITreeViewDataProvider constructor( @IFileService private readonly fileService: IFileService, + @IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ) { @@ -648,31 +777,18 @@ class UserDataSyncTroubleshootViewDataProvider implements ITreeViewDataProvider } private async getSyncLogs(): Promise { - const logsFolders: URI[] = []; - const stat = await this.fileService.resolve(this.uriIdentityService.extUri.dirname(this.uriIdentityService.extUri.dirname(this.environmentService.logsHome))); - if (stat.children) { - logsFolders.push(...stat.children - .filter(stat => stat.isDirectory && /^\d{8}T\d{6}$/.test(stat.name)) - .sort() - .reverse() - .map(d => d.resource)); - } - + const logResources = await this.userDataSyncWorkbenchService.getAllLogResources(); const result: ITreeItem[] = []; - for (const logFolder of logsFolders) { - const folderStat = await this.fileService.resolve(logFolder); - const childStat = folderStat.children?.find(stat => this.uriIdentityService.extUri.basename(stat.resource).startsWith(`${USER_DATA_SYNC_LOG_ID}.`)); - if (childStat) { - const syncLogResource = childStat.resource; - result.push({ - handle: syncLogResource.toString(), - collapsibleState: TreeItemCollapsibleState.None, - resourceUri: syncLogResource, - label: { label: this.uriIdentityService.extUri.basename(logFolder) }, - description: this.uriIdentityService.extUri.isEqual(logFolder, this.environmentService.logsHome) ? localize({ key: 'current', comment: ['Represents current log file'] }, "Current") : undefined, - command: { id: API_OPEN_EDITOR_COMMAND_ID, title: '', arguments: [syncLogResource, undefined, undefined] }, - }); - } + for (const syncLogResource of logResources) { + const logFolder = this.uriIdentityService.extUri.dirname(syncLogResource); + result.push({ + handle: syncLogResource.toString(), + collapsibleState: TreeItemCollapsibleState.None, + resourceUri: syncLogResource, + label: { label: this.uriIdentityService.extUri.basename(logFolder) }, + description: this.uriIdentityService.extUri.isEqual(logFolder, this.environmentService.logsHome) ? localize({ key: 'current', comment: ['Represents current log file'] }, "Current") : undefined, + command: { id: API_OPEN_EDITOR_COMMAND_ID, title: '', arguments: [syncLogResource, undefined, undefined] }, + }); } return result; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 535da4b66c8..a860270eca9 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -29,7 +29,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib/webview/browser/resourceLoading'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { areWebviewContentOptionsEqual, IWebview, WebviewContentOptions, WebviewExtensionDescription, WebviewInitInfo, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 7a348c60a35..352f575bc2d 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -25,6 +25,7 @@ import { Action2, IAction2Options, MenuId } from 'vs/platform/actions/common/act import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { isMacintosh } from 'vs/base/common/platform'; export class CloseWindowAction extends Action2 { @@ -276,26 +277,59 @@ export class QuickSwitchWindowAction extends BaseSwitchWindow { } } +function canRunNativeTabsHandler(accessor: ServicesAccessor): boolean { + if (!isMacintosh) { + return false; + } + + const configurationService = accessor.get(IConfigurationService); + return configurationService.getValue('window.nativeTabs') === true; +} + export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).newWindowTab(); }; export const ShowPreviousWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).showPreviousWindowTab(); }; export const ShowNextWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).showNextWindowTab(); }; export const MoveWindowTabToNewWindowHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).moveWindowTabToNewWindow(); }; export const MergeWindowTabsHandlerHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).mergeAllWindowTabs(); }; export const ToggleWindowTabsBarHandler: ICommandHandler = function (accessor: ServicesAccessor) { + if (!canRunNativeTabsHandler(accessor)) { + return; + } + return accessor.get(INativeHostService).toggleWindowTabsBar(); }; diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 7ae54948297..fda561a69e5 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -222,12 +222,6 @@ import { applicationConfigurationNodeBase } from 'vs/workbench/common/configurat 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") }, - 'window.experimental.nativeContextMenuLocation': { // TODO@bpasero remove me eventually - 'type': 'boolean', - 'default': true, - 'scope': ConfigurationScope.APPLICATION, - 'description': localize('nativeContextMenuLocation', "Let the OS handle positioning of the context menu in cases where it should appear under the mouse.") - }, 'window.dialogStyle': { 'type': 'string', 'enum': ['native', 'custom'], diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index d2a26882677..d6522c913f6 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -190,7 +190,7 @@ export class DesktopMain extends Disposable { ...this.configuration.loggers.global.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource) })), ...this.configuration.loggers.window.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource), hidden: true })), ]; - const loggerService = new LoggerChannelClient(this.configuration.windowId, this.configuration.logLevel, environmentService.logsHome, loggers, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(this.configuration.windowId, this.configuration.logLevel, environmentService.windowLogsPath, loggers, mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); // Log @@ -237,10 +237,6 @@ export class DesktopMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(mainProcessService, utilityProcessWorkerWorkbenchService, logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // Use FileUserDataProvider for user data to - // enable atomic read / write operations. - fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, logService))); - // URI Identity const uriIdentityService = new UriIdentityService(fileService); serviceCollection.set(IUriIdentityService, uriIdentityService); @@ -248,9 +244,13 @@ export class DesktopMain extends Disposable { // User Data Profiles const userDataProfilesService = new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home).with({ scheme: environmentService.userRoamingDataHome.scheme }), mainProcessService.getChannel('userDataProfiles')); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.profile, userDataProfilesService.profilesHome.scheme), userDataProfilesService); + const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.profile, userDataProfilesService.profilesHome.scheme)); serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Use FileUserDataProvider for user data to + // enable atomic read / write operations. + fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService))); + // Remote Agent const remoteSocketFactoryService = new RemoteSocketFactoryService(); remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, new BrowserSocketFactory(null)); diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 1efcf6ba71f..ff8a3457da1 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -725,7 +725,7 @@ export class NativeWindow extends Disposable { // Windows 32-bit warning if (isWindows && this.environmentService.os.arch === 'ia32') { - const message = localize('windows32eolmessage', "{0} on Windows 32-bit will soon stop receiving updates. Consider upgrading to the 64-bit build.", this.productService.nameLong); + const message = localize('windows32eolmessage', "You are running {0} 32-bit, which will soon stop receiving updates on Windows. Consider upgrading to the 64-bit build.", this.productService.nameLong); const actions = [{ label: localize('windowseolBannerLearnMore', "Learn More"), href: 'https://aka.ms/vscode-faq-old-windows' @@ -754,6 +754,45 @@ export class NativeWindow extends Disposable { ); } + // macOS 10.13 and 10.14 warning + if (isMacintosh) { + const majorVersion = this.environmentService.os.release.split('.')[0]; + const eolReleases = new Map([ + ['17', 'macOS High Sierra'], + ['18', 'macOS Mojave'], + ]); + + if (eolReleases.has(majorVersion)) { + const message = localize('macoseolmessage', "{0} on {1} will soon stop receiving updates. Consider upgrading your macOS version.", this.productService.nameLong, eolReleases.get(majorVersion)); + const actions = [{ + label: localize('macoseolBannerLearnMore', "Learn More"), + href: 'https://aka.ms/vscode-faq-old-macOS' + }]; + + this.bannerService.show({ + id: 'macoseol.banner', + message, + ariaLabel: localize('macoseolarialabel', "{0}. Use navigation keys to access banner actions.", message), + actions, + icon: Codicon.warning + }); + + this.notificationService.prompt( + Severity.Warning, + message, + [{ + label: localize('learnMore', "Learn More"), + run: () => this.openerService.open(URI.parse('https://aka.ms/vscode-faq-old-macOS')) + }], + { + neverShowAgain: { id: 'macoseol', isSecondary: true, scope: NeverShowAgainScope.APPLICATION }, + priority: NotificationPriority.URGENT, + sticky: true + } + ); + } + } + // Slow shell environment progress indicator const shellEnv = process.shellEnv(); this.progressService.withProgress({ diff --git a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts index a13b9e59902..1b9393eb4c8 100644 --- a/src/vs/workbench/services/configuration/test/browser/configuration.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configuration.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { DefaultConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; @@ -25,6 +26,7 @@ class ConfigurationCache implements IConfigurationCache { suite('DefaultConfiguration', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const configurationRegistry = Registry.as(Extensions.Configuration); const cacheKey: ConfigurationKey = { type: 'defaults', key: 'configurationDefaultsOverrides' }; let configurationCache: ConfigurationCache; @@ -51,7 +53,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are read from environment', async () => { const environmentService = new BrowserWorkbenchEnvironmentService('', joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), { configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); - const testObject = new DefaultConfiguration(configurationCache, environmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, environmentService)); await testObject.initialize(); assert.deepStrictEqual(testObject.configurationModel.getValue('test.configurationDefaultsOverride'), 'envOverrideValue'); }); @@ -59,7 +61,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are read from cache', async () => { window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); const actual = await testObject.initialize(); @@ -70,7 +72,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are not read from cache when model is read before initialize', async () => { window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); assert.deepStrictEqual(testObject.configurationModel.getValue('test.configurationDefaultsOverride'), undefined); }); @@ -78,7 +80,7 @@ suite('DefaultConfiguration', () => { const environmentService = new BrowserWorkbenchEnvironmentService('', joinPath(URI.file('tests').with({ scheme: 'vscode-tests' }), 'logs'), { configurationDefaults: { 'test.configurationDefaultsOverride': 'envOverrideValue' } }, TestProductService); window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, environmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, environmentService)); const actual = await testObject.initialize(); @@ -88,7 +90,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are read from cache when default configuration changed', async () => { window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); @@ -110,7 +112,7 @@ suite('DefaultConfiguration', () => { test('configuration default overrides are not read from cache after reload', async () => { window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); await testObject.initialize(); const actual = testObject.reload(); @@ -121,7 +123,7 @@ suite('DefaultConfiguration', () => { test('cache is reset after reload', async () => { window.localStorage.setItem(DefaultConfiguration.DEFAULT_OVERRIDES_CACHE_EXISTS_KEY, 'yes'); await configurationCache.write(cacheKey, JSON.stringify({ 'test.configurationDefaultsOverride': 'overrideValue' })); - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); await testObject.initialize(); testObject.reload(); @@ -130,7 +132,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are written in cache', async () => { - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); await testObject.initialize(); testObject.reload(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); @@ -142,7 +144,7 @@ suite('DefaultConfiguration', () => { }); test('configuration default overrides are removed from cache if there are no overrides', async () => { - const testObject = new DefaultConfiguration(configurationCache, TestEnvironmentService); + const testObject = disposables.add(new DefaultConfiguration(configurationCache, TestEnvironmentService)); await testObject.initialize(); const promise = Event.toPromise(testObject.onDidChangeConfiguration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index 9a87d174025..14b3427e048 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -113,9 +113,9 @@ suite('ConfigurationEditing', () => { instantiationService.stub(IEnvironmentService, environmentService); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService)); - disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); + disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); 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 72084f75018..38aa9170fae 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -86,10 +86,10 @@ suite('WorkspaceContextService - Folder', () => { await fileService.createFolder(folder); const environmentService = TestEnvironmentService; - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -129,10 +129,10 @@ suite('WorkspaceContextService - Folder', () => { await fileService.createFolder(folder); const environmentService = TestEnvironmentService; - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -152,10 +152,10 @@ suite('WorkspaceContextService - Folder', () => { await fileService.createFolder(folder); const environmentService = TestEnvironmentService; - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile); const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(new RemoteSocketFactoryService(), userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(false, undefined, undefined, TestProductService, logService), new SignService(TestProductService), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); @@ -202,10 +202,10 @@ suite('WorkspaceContextService - Workspace', () => { const environmentService = TestEnvironmentService; const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService)); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -262,10 +262,10 @@ suite('WorkspaceContextService - Workspace Editing', () => { const environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -508,10 +508,10 @@ suite('WorkspaceService - Initialization', () => { environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -769,10 +769,10 @@ suite('WorkspaceConfigurationService - Folder', () => { environmentService.policyFile = joinPath(folder, 'policies.json'); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1570,10 +1570,10 @@ suite('WorkspaceConfigurationService - Profiles', () => { environmentService.policyFile = joinPath(folder, 'policies.json'); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', 'custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp'), joinPath(environmentService.cacheHome, 'profilesCache')), userDataProfilesService)); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', 'custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp'), joinPath(environmentService.cacheHome, 'profilesCache')))); workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1922,10 +1922,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { environmentService = TestEnvironmentService; const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); @@ -2662,11 +2662,11 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { environmentService = TestEnvironmentService; const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: remoteSettingsResource })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); - fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); - userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); + fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))); + userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile)); testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 66b9fb03b19..e05e7d2519c 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -54,7 +54,7 @@ export class ContextMenuService implements IContextMenuService { // Native context menu: otherwise else { - this.impl = new NativeContextMenuService(notificationService, telemetryService, keybindingService, menuService, contextKeyService, configurationService); + this.impl = new NativeContextMenuService(notificationService, telemetryService, keybindingService, menuService, contextKeyService); } } @@ -77,28 +77,14 @@ class NativeContextMenuService extends Disposable implements IContextMenuService private readonly _onDidHideContextMenu = this._store.add(new Emitter()); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; - private useNativeContextMenuLocation = false; - constructor( @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); - - this.updateUseNativeContextMenuLocation(); - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('window.experimental.nativeContextMenuLocation')) { - this.updateUseNativeContextMenuLocation(); - } - })); - } - - private updateUseNativeContextMenuLocation(): void { - this.useNativeContextMenuLocation = this.configurationService.getValue('window.experimental.nativeContextMenuLocation') === true; } showContextMenu(delegate: IContextMenuDelegate | IContextMenuMenuDelegate): void { @@ -173,13 +159,8 @@ class NativeContextMenuService extends Disposable implements IContextMenuService x = anchor.x; y = anchor.y; } else { - if (this.useNativeContextMenuLocation) { - // We leave x/y undefined in this case which will result in - // Electron taking care of opening the menu at the cursor position. - } else { - x = anchor.posx + 1; // prevent first item from being selected automatically under mouse - y = anchor.posy; - } + // We leave x/y undefined in this case which will result in + // Electron taking care of opening the menu at the cursor position. } if (typeof x === 'number') { diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index e05cee676fc..e2881cf20cc 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, createEditorPart } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, createEditorPart, ITestInstantiationService, workbenchTeardown } from 'vs/workbench/test/browser/workbenchTestServices'; import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupModelChangeKind, SideBySideEditor } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; @@ -17,6 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { IGroupModelChangeEvent, IGroupEditorMoveEvent, IGroupEditorOpenEvent } from 'vs/workbench/common/editor/editorGroupModel'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('EditorGroupsService', () => { @@ -25,11 +26,18 @@ suite('EditorGroupsService', () => { const disposables = new DisposableStore(); + let testLocalInstantiationService: ITestInstantiationService | undefined = undefined; + setup(() => { disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(SideBySideEditorInput)], TEST_EDITOR_INPUT_ID)); }); - teardown(() => { + teardown(async () => { + if (testLocalInstantiationService) { + await workbenchTeardown(testLocalInstantiationService); + testLocalInstantiationService = undefined; + } + disposables.clear(); }); @@ -37,9 +45,15 @@ suite('EditorGroupsService', () => { const part = await createEditorPart(instantiationService, disposables); instantiationService.stub(IEditorGroupsService, part); + testLocalInstantiationService = instantiationService; + return [part, instantiationService]; } + function createTestFileEditorInput(resource: URI, typeId: string): TestFileEditorInput { + return disposables.add(new TestFileEditorInput(resource, typeId)); + } + test('groups basics', async function () { const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); const [part] = await createPart(instantiationService); @@ -123,9 +137,9 @@ suite('EditorGroupsService', () => { const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); let didDispose = false; - downGroup.onWillDispose(() => { + disposables.add(downGroup.onWillDispose(() => { didDispose = true; - }); + })); assert.strictEqual(groupAddedCounter, 2); assert.strictEqual(part.groups.length, 3); assert.ok(part.activeGroup === rightGroup); @@ -201,9 +215,9 @@ suite('EditorGroupsService', () => { const rootGroup = part.activeGroup; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, { pinned: true }); await part.sideGroup.openEditor(input2, { pinned: true }); @@ -221,10 +235,10 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); - const rootGroupInput = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const rootGroupInput = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(rootGroupInput, { pinned: true }); - const rightGroupInput = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const rightGroupInput = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await rightGroup.openEditor(rightGroupInput, { pinned: true }); assert.strictEqual(part.groups.length, 3); @@ -317,7 +331,7 @@ suite('EditorGroupsService', () => { rootGroupDisposed = true; }); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input, { pinned: true }); const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -346,9 +360,9 @@ suite('EditorGroupsService', () => { const rootGroup = part.groups[0]; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, { pinned: true }); @@ -382,15 +396,15 @@ suite('EditorGroupsService', () => { let oldOptions!: IEditorPartOptions; let newOptions!: IEditorPartOptions; - part.onDidChangeEditorPartOptions(event => { + disposables.add(part.onDidChangeEditorPartOptions(event => { oldOptions = event.oldPartOptions; newOptions = event.newPartOptions; - }); + })); const currentOptions = part.partOptions; assert.ok(currentOptions); - part.enforcePartOptions({ showTabs: false }); + disposables.add(part.enforcePartOptions({ showTabs: false })); assert.strictEqual(part.partOptions.showTabs, false); assert.strictEqual(newOptions.showTabs, false); assert.strictEqual(oldOptions, currentOptions); @@ -449,8 +463,8 @@ suite('EditorGroupsService', () => { editorDidCloseCounter++; }); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input, { pinned: true }); await group.openEditor(inputInactive, { inactive: true }); @@ -536,8 +550,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input, options: { pinned: true } }, @@ -564,7 +578,7 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); input.dirty = true; await group.openEditor(input); @@ -589,8 +603,8 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); await rightGroup.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); @@ -615,10 +629,10 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await group.openEditor(input1); await group.openEditor(input2); @@ -643,9 +657,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true } }, @@ -668,9 +682,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true, sticky: true } }, @@ -703,9 +717,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true } }, @@ -727,9 +741,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true, sticky: true } }, @@ -758,9 +772,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true } }, @@ -784,9 +798,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true, sticky: true } }, @@ -817,9 +831,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true } }, @@ -843,9 +857,9 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input1, options: { pinned: true, sticky: true } }, @@ -877,8 +891,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input, options: { pinned: true } }, @@ -902,10 +916,10 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await group.openEditor(input1); await group.openEditor(input2); @@ -930,8 +944,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([ { editor: input, options: { pinned: true, sticky: true } }, @@ -957,8 +971,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); const moveEvents: IGroupModelChangeEvent[] = []; const editorGroupModelChangeListener = group.onDidModelChange(e => { @@ -998,8 +1012,8 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.strictEqual(group.count, 2); @@ -1019,9 +1033,9 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3, options: { pinned: true } }]); assert.strictEqual(group.getEditorByIndex(0), input1); @@ -1042,8 +1056,8 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.strictEqual(group.count, 2); @@ -1064,9 +1078,9 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3, options: { pinned: true } }]); assert.strictEqual(group.getEditorByIndex(0), input1); @@ -1085,8 +1099,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input); assert.strictEqual(group.count, 1); @@ -1105,10 +1119,10 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await group.openEditor(input1); assert.strictEqual(group.activeEditor, input1); @@ -1134,10 +1148,10 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await group.openEditor(input1); assert.strictEqual(group.activeEditor, input1); @@ -1158,15 +1172,15 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); - const input4 = new TestFileEditorInput(URI.file('foo/bar4'), TEST_EDITOR_INPUT_ID); - const input5 = new TestFileEditorInput(URI.file('foo/bar5'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input4 = createTestFileEditorInput(URI.file('foo/bar4'), TEST_EDITOR_INPUT_ID); + const input5 = createTestFileEditorInput(URI.file('foo/bar5'), TEST_EDITOR_INPUT_ID); - const input6 = new TestFileEditorInput(URI.file('foo/bar6'), TEST_EDITOR_INPUT_ID); - const input7 = new TestFileEditorInput(URI.file('foo/bar7'), TEST_EDITOR_INPUT_ID); - const input8 = new TestFileEditorInput(URI.file('foo/bar8'), TEST_EDITOR_INPUT_ID); + const input6 = createTestFileEditorInput(URI.file('foo/bar6'), TEST_EDITOR_INPUT_ID); + const input7 = createTestFileEditorInput(URI.file('foo/bar7'), TEST_EDITOR_INPUT_ID); + const input8 = createTestFileEditorInput(URI.file('foo/bar8'), TEST_EDITOR_INPUT_ID); await group.openEditor(input1, { pinned: true }); await group.openEditor(input2, { pinned: true }); @@ -1192,7 +1206,7 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); const sideBySideInput = instantiationService.createInstance(SideBySideEditorInput, undefined, undefined, input, input); await group.openEditor(input); @@ -1214,11 +1228,11 @@ suite('EditorGroupsService', () => { const group2 = part.addGroup(group, GroupDirection.RIGHT); assert.strictEqual(group.isEmpty, true); - const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.file('foo/bar1'), `${TEST_EDITOR_INPUT_ID}-1`); - const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); - const input4 = new TestFileEditorInput(URI.file('foo/bar4'), TEST_EDITOR_INPUT_ID); - const input5 = new TestFileEditorInput(URI.file('foo/bar4'), `${TEST_EDITOR_INPUT_ID}-1`); + const input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.file('foo/bar1'), `${TEST_EDITOR_INPUT_ID}-1`); + const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); + const input4 = createTestFileEditorInput(URI.file('foo/bar4'), TEST_EDITOR_INPUT_ID); + const input5 = createTestFileEditorInput(URI.file('foo/bar4'), `${TEST_EDITOR_INPUT_ID}-1`); await group.openEditor(input1, { pinned: true }); await group.openEditor(input2, { pinned: true }); @@ -1240,8 +1254,8 @@ suite('EditorGroupsService', () => { const group = part.activeGroup; assert.strictEqual(group.isEmpty, true); - const secondaryInput = new TestFileEditorInput(URI.file('foo/bar-secondary'), TEST_EDITOR_INPUT_ID); - const primaryInput = new TestFileEditorInput(URI.file('foo/bar-primary'), `${TEST_EDITOR_INPUT_ID}-1`); + const secondaryInput = createTestFileEditorInput(URI.file('foo/bar-secondary'), TEST_EDITOR_INPUT_ID); + const primaryInput = createTestFileEditorInput(URI.file('foo/bar-primary'), `${TEST_EDITOR_INPUT_ID}-1`); const sideBySideEditor = new SideBySideEditorInput(undefined, undefined, secondaryInput, primaryInput, accessor.editorService); await group.openEditor(sideBySideEditor, { pinned: true }); @@ -1351,8 +1365,8 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).length, 0); assert.strictEqual(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true }).length, 0); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input, { pinned: true }); await group.openEditor(inputInactive, { inactive: true }); @@ -1414,7 +1428,7 @@ suite('EditorGroupsService', () => { assert.strictEqual(group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).length, 1); assert.strictEqual(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true }).length, 1); - const inputSticky = new TestFileEditorInput(URI.file('foo/bar/sticky'), TEST_EDITOR_INPUT_ID); + const inputSticky = createTestFileEditorInput(URI.file('foo/bar/sticky'), TEST_EDITOR_INPUT_ID); await group.openEditor(inputSticky, { sticky: true }); @@ -1448,9 +1462,9 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); - const thirdInput = new TestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const thirdInput = createTestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID); let leftFiredCount = 0; const leftGroupListener = group.onWillMoveEditor(() => { @@ -1489,9 +1503,9 @@ suite('EditorGroupsService', () => { const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const secondInput = new TestFileEditorInput(URI.file('foo/bar/second'), TEST_EDITOR_INPUT_ID); - const thirdInput = new TestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const secondInput = createTestFileEditorInput(URI.file('foo/bar/second'), TEST_EDITOR_INPUT_ID); + const thirdInput = createTestFileEditorInput(URI.file('foo/bar/third'), TEST_EDITOR_INPUT_ID); let leftFiredCount = 0; const leftGroupListener = group.onWillOpenEditor(() => { @@ -1532,8 +1546,8 @@ suite('EditorGroupsService', () => { const moveListener = group.onWillMoveEditor(() => firedCount++); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); - const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = createTestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.strictEqual(firedCount, 0); @@ -1648,8 +1662,8 @@ suite('EditorGroupsService', () => { const rootGroup = part.activeGroup; let rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - let input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - let input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + let input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + let input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); // First editor opens in right group: Locked=true await rightGroup.openEditor(input1, { pinned: true }); @@ -1665,8 +1679,8 @@ suite('EditorGroupsService', () => { part.removeGroup(rightGroup); await rootGroup.closeAllEditors(); - input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); - input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + input1 = createTestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, { pinned: true }); assert.strictEqual(rootGroup.isLocked, false); @@ -1677,4 +1691,6 @@ suite('EditorGroupsService', () => { part.removeGroup(leftGroup); assert.strictEqual(rootGroup.isLocked, false); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index f67808e1483..14e96886f5d 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -27,6 +27,7 @@ import { ErrorPlaceholderEditor } from 'vs/workbench/browser/parts/editor/editor import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('EditorService', () => { @@ -35,13 +36,20 @@ suite('EditorService', () => { const disposables = new DisposableStore(); + let testLocalInstantiationService: ITestInstantiationService | undefined = undefined; + setup(() => { disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput), new SyncDescriptor(TestSingletonFileEditorInput)], TEST_EDITOR_INPUT_ID)); disposables.add(registerTestResourceEditor()); disposables.add(registerTestSideBySideEditor()); }); - teardown(() => { + teardown(async () => { + if (testLocalInstantiationService) { + await workbenchTeardown(testLocalInstantiationService); + testLocalInstantiationService = undefined; + } + disposables.clear(); }); @@ -52,14 +60,20 @@ suite('EditorService', () => { const editorService = disposables.add(instantiationService.createInstance(EditorService)); instantiationService.stub(IEditorService, editorService); + testLocalInstantiationService = instantiationService; + return [part, editorService, instantiationService.createInstance(TestServiceAccessor)]; } + function createTestFileEditorInput(resource: URI, typeId: string): TestFileEditorInput { + return disposables.add(new TestFileEditorInput(resource, typeId)); + } + test('openEditor() - basics', async () => { const [, service] = await createEditorService(); - let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); - let otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); + let input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + let otherInput = createTestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventCounter = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -116,8 +130,8 @@ suite('EditorService', () => { assert.strictEqual(0, service.count); // Open again 2 inputs (recreate because disposed) - input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); await service.openEditor(input, { pinned: true }); editor = await service.openEditor(otherInput, { pinned: true }); @@ -136,7 +150,7 @@ suite('EditorService', () => { assert.strictEqual(activeEditorChangeEventCounter, 4); assert.strictEqual(visibleEditorChangeEventCounter, 4); - const stickyInput = new TestFileEditorInput(URI.parse('my://resource3-basics'), TEST_EDITOR_INPUT_ID); + const stickyInput = createTestFileEditorInput(URI.parse('my://resource3-basics'), TEST_EDITOR_INPUT_ID); await service.openEditor(stickyInput, { sticky: true }); assert.strictEqual(3, service.count); @@ -165,8 +179,8 @@ suite('EditorService', () => { test('openEditor() - multiple calls are cancelled and indicated as such', async () => { const [, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventCounter = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -197,7 +211,7 @@ suite('EditorService', () => { test('openEditor() - same input does not cancel previous one - https://github.com/microsoft/vscode/issues/136684', async () => { const [, service] = await createEditorService(); - let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + let input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); let editorP1 = service.openEditor(input, { pinned: true }); let editorP2 = service.openEditor(input, { pinned: true }); @@ -211,8 +225,8 @@ suite('EditorService', () => { assert.ok(editor2.group); await editor2.group.closeAllEditors(); - input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); - const inputSame = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + const inputSame = createTestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); editorP1 = service.openEditor(input, { pinned: true }); editorP2 = service.openEditor(inputSame, { pinned: true }); @@ -227,8 +241,8 @@ suite('EditorService', () => { test('openEditor() - singleton typed editors reveal instead of split', async () => { const [part, service] = await createEditorService(); - const input1 = new TestSingletonFileEditorInput(URI.parse('my://resource-basics1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestSingletonFileEditorInput(URI.parse('my://resource-basics2'), TEST_EDITOR_INPUT_ID); + const input1 = disposables.add(new TestSingletonFileEditorInput(URI.parse('my://resource-basics1'), TEST_EDITOR_INPUT_ID)); + const input2 = disposables.add(new TestSingletonFileEditorInput(URI.parse('my://resource-basics2'), TEST_EDITOR_INPUT_ID)); const input1Group = (await service.openEditor(input1, { pinned: true }))?.group; const input2Group = (await service.openEditor(input2, { pinned: true }, SIDE_GROUP))?.group; @@ -250,7 +264,7 @@ suite('EditorService', () => { { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, { - createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + createEditorInput: editor => ({ editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) } )); @@ -388,7 +402,7 @@ suite('EditorService', () => { { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, { - createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + createEditorInput: editor => ({ editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) } )); @@ -438,7 +452,7 @@ suite('EditorService', () => { { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, { - createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + createEditorInput: editor => ({ editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) } )); @@ -488,7 +502,7 @@ suite('EditorService', () => { { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, { - createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + createEditorInput: editor => ({ editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) } )); @@ -560,19 +574,19 @@ suite('EditorService', () => { editorFactoryCalled++; lastEditorFactoryEditor = editor; - return { editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }; + return { editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }; }, createUntitledEditorInput: untitledEditor => { untitledEditorFactoryCalled++; lastUntitledEditorFactoryEditor = untitledEditor; - return { editor: new TestFileEditorInput(untitledEditor.resource ?? URI.parse(`untitled://my-untitled-editor-${untitledEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; + return { editor: createTestFileEditorInput(untitledEditor.resource ?? URI.parse(`untitled://my-untitled-editor-${untitledEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; }, createDiffEditorInput: diffEditor => { diffEditorFactoryCalled++; lastDiffEditorFactoryEditor = diffEditor; - return { editor: new TestFileEditorInput(URI.file(`diff-editor-${diffEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; + return { editor: createTestFileEditorInput(URI.file(`diff-editor-${diffEditorFactoryCalled}`), TEST_EDITOR_INPUT_ID) }; } } )); @@ -839,7 +853,7 @@ suite('EditorService', () => { { // typed editor, no options, no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }); let typedInput = pane?.input; @@ -862,7 +876,7 @@ suite('EditorService', () => { assert.strictEqual(pane?.group.activeEditor, typedInput); // replaceEditors should work too - const typedEditorReplacement = new TestFileEditorInput(URI.file('file-replaced.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditorReplacement = createTestFileEditorInput(URI.file('file-replaced.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); await service.replaceEditors([{ editor: typedEditor, replacement: typedEditorReplacement @@ -885,7 +899,7 @@ suite('EditorService', () => { // typed editor, no options, no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }); const typedInput = pane?.input; @@ -911,7 +925,7 @@ suite('EditorService', () => { // typed editor, options (no override, sticky: true, preserveFocus: true), no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor, options: { sticky: true, preserveFocus: true } }); assert.strictEqual(pane?.group, rootGroup); @@ -933,7 +947,7 @@ suite('EditorService', () => { // typed editor, options (override default), no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }); assert.strictEqual(pane?.group, rootGroup); @@ -954,7 +968,7 @@ suite('EditorService', () => { // typed editor, options (override: TEST_EDITOR_INPUT_ID), no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor, options: { override: TEST_EDITOR_INPUT_ID } }); assert.strictEqual(pane?.group, rootGroup); @@ -974,7 +988,7 @@ suite('EditorService', () => { // typed editor, options (sticky: true, preserveFocus: true), no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor, options: { sticky: true, preserveFocus: true } }); assert.strictEqual(pane?.group, rootGroup); @@ -996,7 +1010,7 @@ suite('EditorService', () => { // typed editor, options (override: TEST_EDITOR_INPUT_ID, sticky: true, preserveFocus: true), no group { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor, options: { sticky: true, preserveFocus: true, override: TEST_EDITOR_INPUT_ID } }); assert.strictEqual(pane?.group, rootGroup); @@ -1018,7 +1032,7 @@ suite('EditorService', () => { // typed editor, no options, SIDE_GROUP { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }, SIDE_GROUP); assert.strictEqual(accessor.editorGroupService.groups.length, 2); @@ -1039,7 +1053,7 @@ suite('EditorService', () => { // typed editor, options (no override), SIDE_GROUP { - const typedEditor = new TestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.editor-service-override-tests'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }, SIDE_GROUP); assert.strictEqual(accessor.editorGroupService.groups.length, 2); @@ -1235,7 +1249,7 @@ suite('EditorService', () => { // no options, no group { - const typedEditor = new TestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }); assert.strictEqual(pane?.group, rootGroup); @@ -1255,7 +1269,7 @@ suite('EditorService', () => { // no options, SIDE_GROUP { - const typedEditor = new TestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); const pane = await openEditor({ editor: typedEditor }, SIDE_GROUP); assert.strictEqual(accessor.editorGroupService.groups.length, 2); @@ -1280,7 +1294,7 @@ suite('EditorService', () => { // no options, no group { - const typedEditor = new TestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); typedEditor.disableToUntyped = true; const pane = await openEditor({ editor: typedEditor }); @@ -1301,7 +1315,7 @@ suite('EditorService', () => { // no options, SIDE_GROUP { - const typedEditor = new TestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); + const typedEditor = createTestFileEditorInput(URI.file('file.something'), TEST_EDITOR_INPUT_ID); typedEditor.disableToUntyped = true; const pane = await openEditor({ editor: typedEditor }, SIDE_GROUP); @@ -1329,8 +1343,8 @@ suite('EditorService', () => { { const untypedEditor1: IResourceEditorInput = { resource: URI.file('file1.editor-service-override-tests') }; const untypedEditor2: IResourceEditorInput = { resource: URI.file('file2.editor-service-override-tests') }; - const untypedEditor3: EditorInputWithOptions = { editor: new TestFileEditorInput(URI.file('file3.editor-service-override-tests'), TEST_EDITOR_INPUT_ID) }; - const untypedEditor4: EditorInputWithOptions = { editor: new TestFileEditorInput(URI.file('file4.editor-service-override-tests'), TEST_EDITOR_INPUT_ID) }; + const untypedEditor3: EditorInputWithOptions = { editor: createTestFileEditorInput(URI.file('file3.editor-service-override-tests'), TEST_EDITOR_INPUT_ID) }; + const untypedEditor4: EditorInputWithOptions = { editor: createTestFileEditorInput(URI.file('file4.editor-service-override-tests'), TEST_EDITOR_INPUT_ID) }; const untypedEditor5: IResourceEditorInput = { resource: URI.file('file5.editor-service-override-tests') }; const pane = (await service.openEditors([untypedEditor1, untypedEditor2, untypedEditor3, untypedEditor4, untypedEditor5]))[0]; @@ -1408,13 +1422,13 @@ suite('EditorService', () => { { id: TEST_EDITOR_INPUT_ID, label: 'Label', priority: RegisteredEditorPriority.exclusive }, {}, { - createEditorInput: editor => ({ editor: new TestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) + createEditorInput: editor => ({ editor: createTestFileEditorInput(editor.resource, TEST_EDITOR_INPUT_ID) }) } )); // Typed editor - let pane = await service.openEditor(new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID)); - pane = await service.openEditor(new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID), { sticky: true, preserveFocus: true }); + let pane = await service.openEditor(createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID)); + pane = await service.openEditor(createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID), { sticky: true, preserveFocus: true }); assert.strictEqual(pane?.options?.sticky, true); assert.strictEqual(pane?.options?.preserveFocus, true); @@ -1440,8 +1454,8 @@ suite('EditorService', () => { test('isOpen() with side by side editor', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput('sideBySide', '', input, otherInput, service); const editor1 = await service.openEditor(sideBySideInput, { pinned: true }); @@ -1479,9 +1493,9 @@ suite('EditorService', () => { test('openEditors() / replaceEditors()', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); - const replaceInput = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const replaceInput = createTestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); @@ -1496,11 +1510,11 @@ suite('EditorService', () => { test('openEditors() handles workspace trust (typed editors)', async () => { const [part, service, accessor] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); - const input4 = new TestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); + const input4 = createTestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput('side by side', undefined, input3, input4, service); const oldHandler = accessor.workspaceTrustRequestService.requestOpenUrisHandler; @@ -1541,11 +1555,11 @@ suite('EditorService', () => { test('openEditors() ignores trust when `validateTrust: false', async () => { const [part, service, accessor] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1-openEditors'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); - const input3 = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); - const input4 = new TestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID); + const input3 = createTestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); + const input4 = createTestFileEditorInput(URI.parse('my://resource4-openEditors'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput('side by side', undefined, input3, input4, service); const oldHandler = accessor.workspaceTrustRequestService.requestOpenUrisHandler; @@ -1565,7 +1579,7 @@ suite('EditorService', () => { test('openEditors() extracts proper resources from untyped editors for workspace trust', async () => { const [, service, accessor] = await createEditorService(); - const input = { resource: URI.parse('my://resource-openEditors') }; + const input = { resource: URI.file('resource-openEditors') }; const otherInput: IResourceDiffEditorInput = { original: { resource: URI.parse('my://resource2-openEditors') }, modified: { resource: URI.parse('my://resource3-openEditors') } @@ -1593,7 +1607,7 @@ suite('EditorService', () => { test('close editor does not dispose when editor opened in other group', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-close1'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-close1'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -1618,8 +1632,8 @@ suite('EditorService', () => { test('open to the side', async () => { const [part, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -1646,8 +1660,8 @@ suite('EditorService', () => { test('editor group activation', async () => { const [part, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -1677,8 +1691,8 @@ suite('EditorService', () => { test('inactive editor group does not activate when closing editor (#117686)', async () => { const [part, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -1701,8 +1715,8 @@ suite('EditorService', () => { test('active editor change / visible editor change events', async function () { const [part, service] = await createEditorService(); - let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + let input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + let otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventFired = false; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -1752,8 +1766,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 2.) open, open same (forced open) (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1765,8 +1779,8 @@ suite('EditorService', () => { await closeEditorAndWaitForNextToOpen(group, input); // 3.) open, open inactive, close (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1780,8 +1794,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 4.) open, open inactive, close inactive (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1799,8 +1813,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 5.) add group, remove group (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1822,8 +1836,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 6.) open editor in inactive group (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1845,8 +1859,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 7.) activate group (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1872,8 +1886,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 8.) move editor (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1891,8 +1905,8 @@ suite('EditorService', () => { assertVisibleEditorsChangedEvent(true); // 9.) close editor in inactive group (recreate inputs that got disposed) - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -1918,8 +1932,8 @@ suite('EditorService', () => { const [part, service] = await createEditorService(); const rootGroup = part.activeGroup; - let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + let input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + let otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); let editorsChangeEventCounter = 0; async function assertEditorsChangeEvent(fn: () => Promise, expected: number) { @@ -1943,8 +1957,8 @@ suite('EditorService', () => { // close (active) await assertEditorsChangeEvent(() => rootGroup.closeEditor(otherInput), 4); - input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); + input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = createTestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); // open editors await assertEditorsChangeEvent(() => service.openEditors([{ editor: input, options: { pinned: true } }, { editor: otherInput, options: { pinned: true } }]), 5); @@ -1965,7 +1979,7 @@ suite('EditorService', () => { test('two active editor change events when opening editor to the side', async function () { const [, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEvents = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -2009,8 +2023,8 @@ suite('EditorService', () => { test('openEditor returns undefined when inactive', async function () { const [, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-inactive'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-inactive'), TEST_EDITOR_INPUT_ID); const editor = await service.openEditor(input, { pinned: true }); assert.ok(editor); @@ -2022,7 +2036,7 @@ suite('EditorService', () => { test('openEditor shows placeholder when opening fails', async function () { const [, service] = await createEditorService(); - const failingInput = new TestFileEditorInput(URI.parse('my://resource-failing'), TEST_EDITOR_INPUT_ID); + const failingInput = createTestFileEditorInput(URI.parse('my://resource-failing'), TEST_EDITOR_INPUT_ID); failingInput.setFailToOpen(); const failingEditor = await service.openEditor(failingInput); @@ -2032,8 +2046,8 @@ suite('EditorService', () => { test('openEditor shows placeholder when restoring fails', async function () { const [, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); - const failingInput = new TestFileEditorInput(URI.parse('my://resource-failing'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + const failingInput = createTestFileEditorInput(URI.parse('my://resource-failing'), TEST_EDITOR_INPUT_ID); await service.openEditor(input, { pinned: true }); await service.openEditor(failingInput, { inactive: true }); @@ -2046,11 +2060,11 @@ suite('EditorService', () => { test('save, saveAll, revertAll', async function () { const [part, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = true; - const sameInput1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const sameInput1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); sameInput1.dirty = true; const rootGroup = part.activeGroup; @@ -2128,11 +2142,11 @@ suite('EditorService', () => { test('saveAll, revertAll (sticky editor)', async function () { const [, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = true; - const sameInput1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const sameInput1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); sameInput1.dirty = true; await service.openEditor(input1, { pinned: true, sticky: true }); @@ -2180,12 +2194,12 @@ suite('EditorService', () => { async function testSaveRevertUntitled(options: IBaseSaveRevertAllEditorOptions, expectUntitled: boolean, expectScratchpad: boolean) { const [, service] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const untitledInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + const untitledInput = createTestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); untitledInput.dirty = true; untitledInput.capabilities = EditorInputCapabilities.Untitled; - const scratchpadInput = new TestFileEditorInput(URI.parse('my://resource3'), TEST_EDITOR_INPUT_ID); + const scratchpadInput = createTestFileEditorInput(URI.parse('my://resource3'), TEST_EDITOR_INPUT_ID); scratchpadInput.modified = true; scratchpadInput.capabilities = EditorInputCapabilities.Scratchpad | EditorInputCapabilities.Untitled; @@ -2230,9 +2244,9 @@ suite('EditorService', () => { async function testFileDeleteEditorClose(dirty: boolean): Promise { const [part, service, accessor] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); input1.dirty = dirty; - const input2 = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input2.dirty = dirty; const rootGroup = part.activeGroup; @@ -2258,8 +2272,8 @@ suite('EditorService', () => { test('file move asks input to move', async function () { const [part, service, accessor] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); - const movedInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID); + const movedInput = createTestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID); input1.movedEditor = { editor: movedInput }; const rootGroup = part.activeGroup; @@ -2293,8 +2307,8 @@ suite('EditorService', () => { test('file watcher gets installed for out of workspace files', async function () { const [, service, accessor] = await createEditorService(); - const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); await service.openEditor(input1, { pinned: true }); assert.strictEqual(accessor.fileService.watches.length, 1); @@ -2312,8 +2326,8 @@ suite('EditorService', () => { const instantiationService = workbenchInstantiationService({ contextKeyService: instantiationService => instantiationService.createInstance(MockScopableContextKeyService) }, disposables); const [part, service] = await createEditorService(instantiationService); - const input1 = new TestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); - new TestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('file://resource1'), TEST_EDITOR_INPUT_ID); + createTestFileEditorInput(URI.parse('file://resource2'), TEST_EDITOR_INPUT_ID); await service.openEditor(input1, { pinned: true }); @@ -2392,10 +2406,10 @@ suite('EditorService', () => { ); assert.strictEqual(editorCount, 0); - const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource1.txt'), TEST_EDITOR_INPUT_ID).toUntyped(); - const input2 = new TestFileEditorInput(URI.parse('file://test/path/resource2.txt'), TEST_EDITOR_INPUT_ID).toUntyped(); - const input3 = new TestFileEditorInput(URI.parse('file://test/path/resource3.md'), TEST_EDITOR_INPUT_ID).toUntyped(); - const input4 = new TestFileEditorInput(URI.parse('file://test/path/resource4.md'), TEST_EDITOR_INPUT_ID).toUntyped(); + const input1 = createTestFileEditorInput(URI.parse('file://test/path/resource1.txt'), TEST_EDITOR_INPUT_ID).toUntyped(); + const input2 = createTestFileEditorInput(URI.parse('file://test/path/resource2.txt'), TEST_EDITOR_INPUT_ID).toUntyped(); + const input3 = createTestFileEditorInput(URI.parse('file://test/path/resource3.md'), TEST_EDITOR_INPUT_ID).toUntyped(); + const input4 = createTestFileEditorInput(URI.parse('file://test/path/resource4.md'), TEST_EDITOR_INPUT_ID).toUntyped(); assert.ok(input1); assert.ok(input2); @@ -2437,7 +2451,7 @@ suite('EditorService', () => { assert.strictEqual(editorCount, 0); - const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource2.md'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('file://test/path/resource2.md'), TEST_EDITOR_INPUT_ID); const untypedInput1 = input1.toUntyped(); assert.ok(untypedInput1); @@ -2457,8 +2471,8 @@ suite('EditorService', () => { test('closeEditor', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); @@ -2481,8 +2495,8 @@ suite('EditorService', () => { test('closeEditors', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); @@ -2496,8 +2510,8 @@ suite('EditorService', () => { test('findEditors (in group)', async () => { const [part, service] = await createEditorService(); - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); @@ -2532,7 +2546,7 @@ suite('EditorService', () => { // Make sure we don't find editors across groups { - const newEditor = await service.openEditor(new TestFileEditorInput(URI.parse('my://other-group-resource'), TEST_EDITOR_INPUT_ID), { pinned: true, preserveFocus: true }, SIDE_GROUP); + const newEditor = await service.openEditor(createTestFileEditorInput(URI.parse('my://other-group-resource'), TEST_EDITOR_INPUT_ID), { pinned: true, preserveFocus: true }, SIDE_GROUP); const found1 = service.findEditors(input.resource, undefined, newEditor!.group!.id); assert.strictEqual(found1.length, 0); @@ -2557,8 +2571,8 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); // Open editors await service.openEditors([{ editor: input }, { editor: otherInput }]); @@ -2616,8 +2630,8 @@ suite('EditorService', () => { test('findEditors (support side by side via options)', async () => { const [, service] = await createEditorService(); - const secondaryInput = new TestFileEditorInput(URI.parse('my://resource-findEditors-secondary'), TEST_EDITOR_INPUT_ID); - const primaryInput = new TestFileEditorInput(URI.parse('my://resource-findEditors-primary'), TEST_EDITOR_INPUT_ID); + const secondaryInput = createTestFileEditorInput(URI.parse('my://resource-findEditors-secondary'), TEST_EDITOR_INPUT_ID); + const primaryInput = createTestFileEditorInput(URI.parse('my://resource-findEditors-primary'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput(undefined, undefined, secondaryInput, primaryInput, service); @@ -2650,8 +2664,8 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); - const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const input = createTestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = createTestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); const sideBySideInput = new SideBySideEditorInput(undefined, undefined, input, input, service); const otherSideBySideInput = new SideBySideEditorInput(undefined, undefined, otherInput, otherInput, service); @@ -2669,8 +2683,8 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - const input1 = new TestFileEditorInput(URI.parse('my://resource-onDidCloseEditor1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource-onDidCloseEditor2'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource-onDidCloseEditor1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource-onDidCloseEditor2'), TEST_EDITOR_INPUT_ID); await service.openEditor(input1, { pinned: true }); await service.openEditor(input2, { pinned: true }); @@ -2678,9 +2692,9 @@ suite('EditorService', () => { const sidegroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const events: IEditorCloseEvent[] = []; - service.onDidCloseEditor(e => { + disposables.add(service.onDidCloseEditor(e => { events.push(e); - }); + })); rootGroup.moveEditor(input1, sidegroup); @@ -2696,18 +2710,20 @@ suite('EditorService', () => { const rootGroup = part.activeGroup; - const input1 = new TestFileEditorInput(URI.parse('my://resource-onDidCloseEditor1'), TEST_EDITOR_INPUT_ID); - const input2 = new TestFileEditorInput(URI.parse('my://resource-onDidCloseEditor2'), TEST_EDITOR_INPUT_ID); + const input1 = createTestFileEditorInput(URI.parse('my://resource-onDidCloseEditor1'), TEST_EDITOR_INPUT_ID); + const input2 = createTestFileEditorInput(URI.parse('my://resource-onDidCloseEditor2'), TEST_EDITOR_INPUT_ID); await service.openEditor(input1, { pinned: true }); const events: IEditorCloseEvent[] = []; - service.onDidCloseEditor(e => { + disposables.add(service.onDidCloseEditor(e => { events.push(e); - }); + })); await rootGroup.replaceEditors([{ editor: input1, replacement: input2 }]); assert.strictEqual(events[0].context, EditorCloseContext.REPLACE); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index a4105a5dc60..d2f72d46b93 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -138,7 +138,7 @@ suite('EditorsObserver', function () { assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId, editorId: input3.editorId }), false); }); - test.skip('basics (multi group)', async () => { // todo@bpasero + test('basics (multi group)', async () => { const [part, observer] = await createEditorObserver(); const rootGroup = part.activeGroup; @@ -287,7 +287,7 @@ suite('EditorsObserver', function () { assert.strictEqual(observer.hasEditor({ resource: secondary.resource, typeId: secondary.typeId, editorId: secondary.editorId }), false); }); - test.skip('copy group', async function () { // TODO@bpasero + test('copy group', async function () { const [part, observer] = await createEditorObserver(); const input1 = disposables.add(new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID)); diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index 27f37ac508b..551008eec3a 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -89,17 +89,17 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne private async localizeManifest(extensionId: string, manifest: IExtensionManifest, fallbackTranslations: ITranslations): Promise { if (!this.nlsUrl) { - return localizeManifest(manifest, fallbackTranslations); + return localizeManifest(this.logService, manifest, fallbackTranslations); } // the `package` endpoint returns the translations in a key-value format similar to the package.nls.json file. const uri = URI.joinPath(this.nlsUrl, extensionId, 'package'); try { const res = await this.extensionResourceLoaderService.readExtensionResource(uri); const json = JSON.parse(res.toString()); - return localizeManifest(manifest, json, fallbackTranslations); + return localizeManifest(this.logService, manifest, json, fallbackTranslations); } catch (e) { this.logService.error(e); - return localizeManifest(manifest, fallbackTranslations); + return localizeManifest(this.logService, manifest, fallbackTranslations); } } } diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index a2426291881..8cc185f5da7 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -801,7 +801,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten const translations = URI.isUri(nlsURL) ? await this.getTranslations(nlsURL) : nlsURL; const fallbackTranslations = URI.isUri(fallbackNLS) ? await this.getTranslations(fallbackNLS) : fallbackNLS; if (translations) { - manifest = localizeManifest(manifest, translations, fallbackTranslations); + manifest = localizeManifest(this.logService, manifest, translations, fallbackTranslations); } } catch (error) { /* ignore */ } return manifest; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 8001988f5ea..5dcc2ffe5a7 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -72,12 +72,12 @@ export class ExtensionManagementService extends Disposable implements IWorkbench this.servers.push(this.extensionManagementServerService.webExtensionManagementServer); } - this.onInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onInstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onDidInstallExtensions = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidInstallExtensions); return emitter; }, new EventMultiplexer())).event; - this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onDidUpdateExtensionMetadata = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidUpdateExtensionMetadata); return emitter; }, new EventMultiplexer())).event; - this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; + this.onInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onInstallExtension, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer()))).event; + this.onDidInstallExtensions = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(server.extensionManagementService.onDidInstallExtensions)); return emitter; }, this._register(new EventMultiplexer()))).event; + this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer()))).event; + this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer()))).event; + this.onDidUpdateExtensionMetadata = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(server.extensionManagementService.onDidUpdateExtensionMetadata)); return emitter; }, this._register(new EventMultiplexer()))).event; + this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { this._register(emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server })))); return emitter; }, this._register(new EventMultiplexer()))).event; } async getInstalled(type?: ExtensionType, profileLocation?: URI): 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 bbfd1d8437a..fa8aff5fde3 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -36,8 +36,9 @@ import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -function createStorageService(instantiationService: TestInstantiationService): IStorageService { +function createStorageService(instantiationService: TestInstantiationService, disposableStore: DisposableStore): IStorageService { let service = instantiationService.get(IStorageService); if (!service) { let workspaceContextService = instantiationService.get(IWorkspaceContextService); @@ -47,34 +48,34 @@ function createStorageService(instantiationService: TestInstantiationService): I getWorkspace: () => TestWorkspace as IWorkspace }); } - service = instantiationService.stub(IStorageService, new InMemoryStorageService()); + service = instantiationService.stub(IStorageService, disposableStore.add(new InMemoryStorageService())); } return service; } export class TestExtensionEnablementService extends ExtensionEnablementService { constructor(instantiationService: TestInstantiationService) { - const storageService = createStorageService(instantiationService); + const disposables = new DisposableStore(); + const storageService = createStorageService(instantiationService, disposables); const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService) || instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService({ id: 'local', label: 'local', extensionManagementService: { - onInstallExtension: new Emitter().event, - onDidInstallExtensions: new Emitter().event, - onUninstallExtension: new Emitter().event, - onDidUninstallExtension: new Emitter().event, - onDidChangeProfile: new Emitter().event, - onDidUpdateExtensionMetadata: new Emitter().event, + onInstallExtension: disposables.add(new Emitter()).event, + onDidInstallExtensions: disposables.add(new Emitter()).event, + onUninstallExtension: disposables.add(new Emitter()).event, + onDidUninstallExtension: disposables.add(new Emitter()).event, + onDidChangeProfile: disposables.add(new Emitter()).event, + onDidUpdateExtensionMetadata: disposables.add(new Emitter()).event, }, }, null, null)); - const extensionManagementService = instantiationService.createInstance(ExtensionManagementService); + const extensionManagementService = disposables.add(instantiationService.createInstance(ExtensionManagementService)); const workbenchExtensionManagementService = instantiationService.get(IWorkbenchExtensionManagementService) || instantiationService.stub(IWorkbenchExtensionManagementService, extensionManagementService); - const disposables = new DisposableStore(); const workspaceTrustManagementService = instantiationService.get(IWorkspaceTrustManagementService) || instantiationService.stub(IWorkspaceTrustManagementService, disposables.add(new TestWorkspaceTrustManagementService())); super( storageService, - new GlobalExtensionEnablementService(storageService, extensionManagementService), + disposables.add(new GlobalExtensionEnablementService(storageService, extensionManagementService)), instantiationService.get(IWorkspaceContextService) || new TestContextService(), instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, {} as IWorkbenchEnvironmentService), workbenchExtensionManagementService, @@ -82,13 +83,13 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { extensionManagementServerService, instantiationService.get(IUserDataSyncEnablementService) || instantiationService.stub(IUserDataSyncEnablementService, >{ isEnabled() { return false; } }), instantiationService.get(IUserDataSyncAccountService) || instantiationService.stub(IUserDataSyncAccountService, UserDataSyncAccountService), - instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, new TestLifecycleService()), + instantiationService.get(ILifecycleService) || instantiationService.stub(ILifecycleService, disposables.add(new TestLifecycleService())), instantiationService.get(INotificationService) || instantiationService.stub(INotificationService, new TestNotificationService()), instantiationService.get(IHostService), new class extends mock() { override isDisabledByBisect() { return false; } }, workspaceTrustManagementService, new class extends mock() { override requestWorkspaceTrust(options?: WorkspaceTrustRequestOptions): Promise { return Promise.resolve(true); } }, - instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService())), + instantiationService.get(IExtensionManifestPropertiesService) || instantiationService.stub(IExtensionManifestPropertiesService, disposables.add(new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService()))), instantiationService ); this._register(disposables); @@ -115,6 +116,8 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { suite('ExtensionEnablementService Test', () => { + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); + let instantiationService: TestInstantiationService; let testObject: IWorkbenchExtensionEnablementService; @@ -125,7 +128,7 @@ suite('ExtensionEnablementService Test', () => { setup(() => { installed.splice(0, installed.length); - instantiationService = new TestInstantiationService(); + instantiationService = disposableStore.add(new TestInstantiationService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService({ id: 'local', @@ -137,13 +140,8 @@ suite('ExtensionEnablementService Test', () => { getInstalled: () => Promise.resolve(installed) }, }, null, null)); - instantiationService.stub(IWorkbenchExtensionManagementService, instantiationService.createInstance(ExtensionManagementService)); - testObject = new TestExtensionEnablementService(instantiationService); - }); - - teardown(() => { - (testObject).dispose(); - instantiationService.dispose(); + instantiationService.stub(IWorkbenchExtensionManagementService, disposableStore.add(instantiationService.createInstance(ExtensionManagementService))); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); }); test('test disable an extension globally', async () => { @@ -160,7 +158,7 @@ suite('ExtensionEnablementService Test', () => { test('test disable an extension globally triggers the change event', async () => { const target = sinon.spy(); - testObject.onEnablementChanged(target); + disposableStore.add(testObject.onEnablementChanged(target)); await testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally); assert.ok(target.calledOnce); assert.deepStrictEqual((target.args[0][0][0]).identifier, { id: 'pub.a' }); @@ -275,7 +273,7 @@ suite('ExtensionEnablementService Test', () => { test('test disable an extension for workspace and then globally trigger the change event', () => { const target = sinon.spy(); return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace) - .then(() => testObject.onEnablementChanged(target)) + .then(() => disposableStore.add(testObject.onEnablementChanged(target))) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally)) .then(() => { assert.ok(target.calledOnce); @@ -300,7 +298,7 @@ suite('ExtensionEnablementService Test', () => { test('test disable an extension globally and then for workspace triggers the change event', () => { const target = sinon.spy(); return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) - .then(() => testObject.onEnablementChanged(target)) + .then(() => disposableStore.add(testObject.onEnablementChanged(target))) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledWorkspace)) .then(() => { assert.ok(target.calledOnce); @@ -331,7 +329,7 @@ suite('ExtensionEnablementService Test', () => { test('test enable an extension globally triggers change event', () => { const target = sinon.spy(); return testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.DisabledGlobally) - .then(() => testObject.onEnablementChanged(target)) + .then(() => disposableStore.add(testObject.onEnablementChanged(target))) .then(() => testObject.setEnablement([aLocalExtension('pub.a')], EnablementState.EnabledGlobally)) .then(() => { assert.ok(target.calledOnce); @@ -361,7 +359,7 @@ suite('ExtensionEnablementService Test', () => { test('test enable an extension for workspace triggers change event', () => { const target = sinon.spy(); return testObject.setEnablement([aLocalExtension('pub.b')], EnablementState.DisabledWorkspace) - .then(() => testObject.onEnablementChanged(target)) + .then(() => disposableStore.add(testObject.onEnablementChanged(target))) .then(() => testObject.setEnablement([aLocalExtension('pub.b')], EnablementState.EnabledWorkspace)) .then(() => { assert.ok(target.calledOnce); @@ -423,7 +421,7 @@ suite('ExtensionEnablementService Test', () => { const remoteWorkspaceDepExtension = aLocalExtension2('pub.b', { extensionKind: ['workspace'] }, { location: URI.file(`pub.b`).with({ scheme: Schemas.vscodeRemote }) }); installed.push(localWorkspaceDepExtension, remoteWorkspaceExtension, remoteWorkspaceDepExtension); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); await testObject.setEnablement([remoteWorkspaceExtension], EnablementState.DisabledGlobally); @@ -447,7 +445,7 @@ suite('ExtensionEnablementService Test', () => { test('test remove an extension from disablement list when uninstalled', async () => { const extension = aLocalExtension('pub.a'); installed.push(extension); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); await testObject.setEnablement([extension], EnablementState.DisabledGlobally); @@ -485,7 +483,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IUserDataSyncAccountService, >{ account: { authenticationProviderId: 'b' } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); }); @@ -493,7 +491,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IUserDataSyncAccountService, >{ account: { authenticationProviderId: 'a' } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), true); }); @@ -502,7 +500,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IUserDataSyncAccountService, >{ account: { authenticationProviderId: 'a' } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a', { authentication: [{ id: 'a', label: 'a' }] })), false); }); @@ -521,26 +519,26 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when extensions are disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return false when the extension is disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); assert.strictEqual(testObject.canChangeEnablement(extension), true); }); test('test canChangeEnablement return false for system extension when extension is disabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); const extension = aLocalExtension('pub.a', undefined, ExtensionType.System); assert.ok(!testObject.canChangeEnablement(extension)); }); @@ -550,7 +548,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment); @@ -561,7 +559,7 @@ suite('ExtensionEnablementService Test', () => { installed.push(extension); instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); @@ -573,7 +571,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.EnabledWorkspace); instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledWorkspace); @@ -585,7 +583,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledGlobally); instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledByEnvironment); @@ -597,7 +595,7 @@ suite('ExtensionEnablementService Test', () => { await testObject.setEnablement([extension], EnablementState.DisabledWorkspace); instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledByEnvironment); @@ -609,7 +607,7 @@ suite('ExtensionEnablementService Test', () => { testObject.setEnablement([extension], EnablementState.DisabledWorkspace); instantiationService.stub(IWorkbenchEnvironmentService, { disableExtensions: true, enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByEnvironment); @@ -617,14 +615,14 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when the extension is enabled in environment', () => { instantiationService.stub(IWorkbenchEnvironmentService, { enableExtensions: ['pub.a'] } as IWorkbenchEnvironmentService); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(aLocalExtension('pub.a')), false); }); test('test extension does not support vitrual workspace is not enabled in virtual workspace', async () => { const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByVirtualWorkspace); }); @@ -633,7 +631,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false }, browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'web' }) }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); @@ -642,7 +640,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false }, browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByVirtualWorkspace); }); @@ -652,7 +650,7 @@ suite('ExtensionEnablementService Test', () => { 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); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); installed.push(localUIExtension, remoteUIExtension, target); await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); @@ -668,7 +666,7 @@ suite('ExtensionEnablementService Test', () => { 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); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); installed.push(localUIExtension, remoteUIExtension, target); await testObject.setEnablement([target, localUIExtension], EnablementState.DisabledGlobally); @@ -682,14 +680,14 @@ suite('ExtensionEnablementService Test', () => { 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' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.canChangeEnablement(extension)); }); test('test extension does not support vitrual workspace is enabled in normal workspace', async () => { const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA') }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); @@ -697,7 +695,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension supports virtual workspace is enabled in virtual workspace', async () => { const extension = aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: true } }); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); @@ -705,42 +703,42 @@ suite('ExtensionEnablementService Test', () => { test('test extension does not support untrusted workspaces is disabled in untrusted workspace', () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: 'hello' } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementState(extension), EnablementState.DisabledByTrustRequirement); }); test('test canChangeEnablement return true when extension is disabled by workspace trust', () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: 'hello' } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.canChangeEnablement(extension)); }); test('test extension supports untrusted workspaces is enabled in untrusted workspace', () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: true } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test extension does not support untrusted workspaces is enabled in trusted workspace', () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: '' } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return true; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test extension supports untrusted workspaces is enabled in trusted workspace', () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: true } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return true; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); test('test extension without any value for virtual worksapce is enabled in virtual workspace', async () => { const extension = aLocalExtension2('pub.a'); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(extension)); assert.deepStrictEqual(testObject.getEnablementState(extension), EnablementState.EnabledGlobally); }); @@ -748,7 +746,7 @@ suite('ExtensionEnablementService Test', () => { test('test local workspace extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -756,7 +754,7 @@ suite('ExtensionEnablementService Test', () => { test('test local workspace + ui extension is enabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace', 'ui'] }, { location: URI.file(`pub.a`) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -764,7 +762,7 @@ suite('ExtensionEnablementService Test', () => { test('test local ui extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -772,21 +770,21 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return true when the local workspace extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for local ui extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test remote ui extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -794,7 +792,7 @@ suite('ExtensionEnablementService Test', () => { test('test remote ui+workspace extension is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui', 'workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -802,7 +800,7 @@ suite('ExtensionEnablementService Test', () => { test('test remote ui extension is disabled by kind when there is no local server', async () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService), null)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(!testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -810,7 +808,7 @@ suite('ExtensionEnablementService Test', () => { test('test remote workspace extension is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -818,14 +816,14 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return true when the remote ui extension is disabled by kind', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['ui'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), false); }); test('test canChangeEnablement return true for remote workspace extension', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['workspace'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.canChangeEnablement(localWorkspaceExtension), true); }); @@ -833,7 +831,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -842,7 +840,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), true); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -851,7 +849,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); const localWorkspaceExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -860,7 +858,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), null)); const localWorkspaceExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: true }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), false); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); @@ -869,7 +867,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); const localWorkspaceExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'vscode-remote' }) }); (instantiationService.get(IConfigurationService)).setUserConfiguration('extensions', { webWorker: false }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(localWorkspaceExtension), true); assert.deepStrictEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); @@ -877,14 +875,14 @@ suite('ExtensionEnablementService Test', () => { test('test web extension on web server is not disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(anExtensionManagementServer('vscode-local', instantiationService), anExtensionManagementServer('vscode-remote', instantiationService), anExtensionManagementServer('web', instantiationService))); const webExtension = aLocalExtension2('pub.a', { browser: 'browser.js' }, { location: URI.file(`pub.a`).with({ scheme: 'web' }) }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.isEnabled(webExtension), true); assert.deepStrictEqual(testObject.getEnablementState(webExtension), EnablementState.EnabledGlobally); }); test('test state of multipe extensions', async () => { installed.push(...[aLocalExtension('pub.a'), aLocalExtension('pub.b'), aLocalExtension('pub.c'), aLocalExtension('pub.d'), aLocalExtension('pub.e')]); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); await testObject.setEnablement([installed[0]], EnablementState.DisabledGlobally); @@ -897,7 +895,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension is disabled by dependency if it has a dependency that is disabled', async () => { installed.push(...[aLocalExtension2('pub.a'), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'] })]); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); await testObject.setEnablement([installed[0]], EnablementState.DisabledGlobally); @@ -908,7 +906,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension is disabled by dependency if it has a dependency that is disabled by virtual workspace', async () => { installed.push(...[aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'], capabilities: { virtualWorkspaces: true } })]); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); assert.strictEqual(testObject.getEnablementState(installed[0]), EnablementState.DisabledByVirtualWorkspace); @@ -918,7 +916,7 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return false when extension is disabled by dependency if it has a dependency that is disabled by virtual workspace', async () => { installed.push(...[aLocalExtension2('pub.a', { capabilities: { virtualWorkspaces: false } }), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'], capabilities: { virtualWorkspaces: true } })]); instantiationService.stub(IWorkspaceContextService, 'getWorkspace', { folders: [{ uri: URI.file('worskapceA').with(({ scheme: 'virtual' })) }] }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); assert.ok(!testObject.canChangeEnablement(installed[1])); @@ -927,7 +925,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension is disabled by dependency if it has a dependency that is disabled by workspace trust', async () => { installed.push(...[aLocalExtension2('pub.a', { main: 'hello.js', capabilities: { untrustedWorkspaces: { supported: false, description: '' } } }), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'], capabilities: { untrustedWorkspaces: { supported: true } } })]); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); assert.strictEqual(testObject.getEnablementState(installed[0]), EnablementState.DisabledByTrustRequirement); @@ -941,7 +939,7 @@ suite('ExtensionEnablementService Test', () => { const remoteWorkspaceExtension = aLocalExtension2('pub.n', { extensionKind: ['workspace'], extensionDependencies: ['pub.a'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); installed.push(localUIExtension, remoteUIExtension, remoteWorkspaceExtension); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); assert.strictEqual(testObject.getEnablementState(localUIExtension), EnablementState.EnabledGlobally); @@ -952,7 +950,7 @@ suite('ExtensionEnablementService Test', () => { test('test canChangeEnablement return true when extension is disabled by dependency if it has a dependency that is disabled by workspace trust', async () => { installed.push(...[aLocalExtension2('pub.a', { main: 'hello.js', capabilities: { untrustedWorkspaces: { supported: false, description: '' } } }), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'], capabilities: { untrustedWorkspaces: { supported: true } } })]); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); assert.ok(testObject.canChangeEnablement(installed[1])); @@ -966,7 +964,7 @@ suite('ExtensionEnablementService Test', () => { test('test extension is disabled by dependency if it has a dependency that is disabled when all extensions are passed', async () => { installed.push(...[aLocalExtension2('pub.a'), aLocalExtension2('pub.b', { extensionDependencies: ['pub.a'] })]); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await (testObject).waitUntilInitialized(); await testObject.setEnablement([installed[0]], EnablementState.DisabledGlobally); @@ -977,7 +975,7 @@ suite('ExtensionEnablementService Test', () => { test('test override workspace to trusted when getting extensions enablements', async () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: 'hello' } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return false; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementStates([extension], { trusted: true })[0], EnablementState.EnabledGlobally); }); @@ -985,7 +983,7 @@ suite('ExtensionEnablementService Test', () => { test('test override workspace to not trusted when getting extensions enablements', async () => { const extension = aLocalExtension2('pub.a', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: 'hello' } } }); instantiationService.stub(IWorkspaceTrustManagementService, >{ isWorkspaceTrusted() { return true; } }); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); assert.strictEqual(testObject.getEnablementStates([extension], { trusted: false })[0], EnablementState.DisabledByTrustRequirement); }); @@ -997,9 +995,9 @@ suite('ExtensionEnablementService Test', () => { aLocalExtension2('pub.c', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: false, description: 'hello' } } }), aLocalExtension2('pub.d', { main: 'main.js', capabilities: { untrustedWorkspaces: { supported: true } } }), ]); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); const target = sinon.spy(); - testObject.onEnablementChanged(target); + disposableStore.add(testObject.onEnablementChanged(target)); await testObject.updateExtensionsEnablementsWhenWorkspaceTrustChanges(); assert.strictEqual(target.args[0][0].length, 2); @@ -1010,11 +1008,11 @@ suite('ExtensionEnablementService Test', () => { test('test adding an extension that was disabled', async () => { const extension = aLocalExtension('pub.a'); installed.push(extension); - testObject = new TestExtensionEnablementService(instantiationService); + testObject = disposableStore.add(new TestExtensionEnablementService(instantiationService)); await testObject.setEnablement([extension], EnablementState.DisabledGlobally); const target = sinon.spy(); - testObject.onEnablementChanged(target); + disposableStore.add(testObject.onEnablementChanged(target)); didChangeProfileExtensionsEvent.fire({ added: [extension], removed: [] }); assert.ok(!testObject.isEnabled(extension)); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index d8652bf5ac0..0317de83928 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -74,7 +74,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten instantiationService, remoteAgentService, remoteAuthorityResolverService, - extensionEnablementService + extensionEnablementService, + logService ); super( extensionsProposedApi, @@ -220,6 +221,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, + @ILogService private readonly _logService: ILogService, ) { } createExtensionHost(runningLocations: ExtensionRunningLocationTracker, runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null { @@ -250,7 +252,7 @@ class BrowserExtensionHostFactory implements IExtensionHostFactory { getInitData: async (): Promise => { if (isInitialStart) { // Here we load even extensions that would be disabled by workspace trust - const localExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true); + const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, await this._scanWebExtensions(), /* ignore workspace trust */true); const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); return { diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index ad66755797e..5ec7f0690c6 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -25,7 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { ExtensionHostExitCode, IExtensionHostInitData, MessageType, UIKind, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensionRunningLocation'; diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index c529886ca92..ccb729ce206 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -9,6 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as perf from 'vs/base/common/performance'; +import { isCI } from 'vs/base/common/platform'; import { isEqualOrParent } from 'vs/base/common/resources'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; @@ -138,11 +139,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx toRemove.push(extension); } } + if (isCI) { + this._logService.info(`AbstractExtensionService.onEnablementChanged fired for ${extensions.map(e => e.identifier.id).join(', ')}`); + } this._handleDeltaExtensions(new DeltaExtensionsQueueItem(toAdd, toRemove)); })); this._register(this._extensionManagementService.onDidChangeProfile(({ added, removed }) => { if (added.length || removed.length) { + if (isCI) { + this._logService.info(`AbstractExtensionService.onDidChangeProfile fired`); + } this._handleDeltaExtensions(new DeltaExtensionsQueueItem(added, removed)); } })); @@ -155,6 +162,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } if (extensions.length) { + if (isCI) { + this._logService.info(`AbstractExtensionService.onDidInstallExtensions fired for ${extensions.map(e => e.identifier.id).join(', ')}`); + } this._handleDeltaExtensions(new DeltaExtensionsQueueItem(extensions, [])); } })); @@ -162,6 +172,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._register(this._extensionManagementService.onDidUninstallExtension((event) => { if (!event.error) { // an extension has been uninstalled + if (isCI) { + this._logService.info(`AbstractExtensionService.onDidUninstallExtension fired for ${event.identifier.id}`); + } this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id])); } })); @@ -218,6 +231,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } private async _deltaExtensions(lock: ExtensionDescriptionRegistryLock, _toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise { + if (isCI) { + this._logService.info(`AbstractExtensionService._deltaExtensions: toAdd: [${_toAdd.map(e => e.identifier.id).join(',')}] toRemove: [${_toRemove.map(e => typeof e === 'string' ? e : e.identifier.id).join(',')}]`); + } let toRemove: IExtensionDescription[] = []; for (let i = 0, len = _toRemove.length; i < len; i++) { const extensionOrId = _toRemove[i]; @@ -301,6 +317,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx const myToAdd = this._runningLocations.filterByExtensionHostManager(toAdd, extensionHostManager); const myToRemove = filterExtensionIdentifiers(toRemove, removedRunningLocation, extRunningLocation => extensionHostManager.representsRunningLocation(extRunningLocation)); const addActivationEvents = ImplicitActivationEvents.createActivationEventsMap(toAdd); + if (isCI) { + const printExtIds = (extensions: IExtensionDescription[]) => extensions.map(e => e.identifier.value).join(','); + const printIds = (extensions: ExtensionIdentifier[]) => extensions.map(e => e.value).join(','); + this._logService.info(`AbstractExtensionService: Calling deltaExtensions: toRemove: [${printIds(toRemove)}], toAdd: [${printExtIds(toAdd)}], myToRemove: [${printIds(myToRemove)}], myToAdd: [${printExtIds(myToAdd)}],`); + } await extensionHostManager.deltaExtensions({ toRemove, toAdd, addActivationEvents, myToRemove, myToAdd: myToAdd.map(extension => extension.identifier) }); } @@ -437,8 +458,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private _processExtensions(lock: ExtensionDescriptionRegistryLock, resolvedExtensions: ResolvedExtensions): void { const { allowRemoteExtensionsInLocalWebWorker, hasLocalProcess } = resolvedExtensions; - const localExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, resolvedExtensions.local, false); - let remoteExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, resolvedExtensions.remote, false); + const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, resolvedExtensions.local, false); + let remoteExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, resolvedExtensions.remote, false); // `initializeRunningLocation` will look at the complete picture (e.g. an extension installed on both sides), // takes care of duplicates and picks a running location for each extension @@ -489,7 +510,13 @@ export abstract class AbstractExtensionService extends Disposable implements IEx let exitCode: number; try { exitCode = await extensionHostManager.extensionTestsExecute(); + if (isCI) { + this._logService.info(`Extension host test runner exit code: ${exitCode}`); + } } catch (err) { + if (isCI) { + this._logService.error(`Extension host test runner error`, err); + } console.error(err); exitCode = 1 /* ERROR */; } @@ -1167,19 +1194,19 @@ class DeltaExtensionsQueueItem { * @argument extensions The extensions to be checked. * @argument ignoreWorkspaceTrust Do not take workspace trust into account. */ -export function checkEnabledAndProposedAPI(extensionEnablementService: IWorkbenchExtensionEnablementService, extensionsProposedApi: ExtensionsProposedApi, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] { +export function checkEnabledAndProposedAPI(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extensionsProposedApi: ExtensionsProposedApi, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] { // enable or disable proposed API per extension extensionsProposedApi.updateEnabledApiProposals(extensions); // keep only enabled extensions - return filterEnabledExtensions(extensionEnablementService, extensions, ignoreWorkspaceTrust); + return filterEnabledExtensions(logService, extensionEnablementService, extensions, ignoreWorkspaceTrust); } /** * Return the subset of extensions that are enabled. * @argument ignoreWorkspaceTrust Do not take workspace trust into account. */ -export function filterEnabledExtensions(extensionEnablementService: IWorkbenchExtensionEnablementService, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] { +export function filterEnabledExtensions(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extensions: IExtensionDescription[], ignoreWorkspaceTrust: boolean): IExtensionDescription[] { const enabledExtensions: IExtensionDescription[] = [], extensionsToCheck: IExtensionDescription[] = [], mappedExtensions: IExtension[] = []; for (const extension of extensions) { if (extension.isUnderDevelopment) { @@ -1195,6 +1222,10 @@ export function filterEnabledExtensions(extensionEnablementService: IWorkbenchEx for (let index = 0; index < enablementStates.length; index++) { if (extensionEnablementService.isEnabledEnablementState(enablementStates[index])) { enabledExtensions.push(extensionsToCheck[index]); + } else { + if (isCI) { + logService.info(`filterEnabledExtensions: extension '${extensionsToCheck[index].identifier.value}' is disabled`); + } } } @@ -1205,8 +1236,8 @@ export function filterEnabledExtensions(extensionEnablementService: IWorkbenchEx * @argument extension The extension to be checked. * @argument ignoreWorkspaceTrust Do not take workspace trust into account. */ -export function extensionIsEnabled(extensionEnablementService: IWorkbenchExtensionEnablementService, extension: IExtensionDescription, ignoreWorkspaceTrust: boolean): boolean { - return filterEnabledExtensions(extensionEnablementService, [extension], ignoreWorkspaceTrust).includes(extension); +export function extensionIsEnabled(logService: ILogService, extensionEnablementService: IWorkbenchExtensionEnablementService, extension: IExtensionDescription, ignoreWorkspaceTrust: boolean): boolean { + return filterEnabledExtensions(logService, extensionEnablementService, [extension], ignoreWorkspaceTrust).includes(extension); } function includes(extensions: IExtensionDescription[], identifier: ExtensionIdentifier): boolean { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 493a5d860a6..ad2039a68bf 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -40,7 +40,6 @@ export const allApiProposals = Object.freeze({ dropMetadata: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.dropMetadata.d.ts', editSessionIdentityProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts', editorInsets: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.editorInsets.d.ts', - envShellEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envShellEvent.d.ts', extensionRuntime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionRuntime.d.ts', extensionsAny: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionsAny.d.ts', externalUriOpener: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.externalUriOpener.d.ts', diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index db6aa3ad6d8..ddd44633910 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -9,7 +9,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { CharCode } from 'vs/base/common/charCode'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { MarshalledObject } from 'vs/base/common/marshalling'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { IURITransformer, transformIncomingURIs } from 'vs/base/common/uriIpc'; @@ -132,7 +132,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { private readonly _proxies: any[]; private _lastMessageId: number; private readonly _cancelInvokedHandlers: { [req: string]: () => void }; - private readonly _pendingRPCReplies: { [msgId: string]: LazyPromise }; + private readonly _pendingRPCReplies: { [msgId: string]: PendingRPCReply }; private _responsiveState: ResponsiveState; private _unacknowledgedCount: number; private _unresponsiveTime: number; @@ -167,6 +167,7 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { // Release all outstanding promises with a canceled error Object.keys(this._pendingRPCReplies).forEach((msgId) => { const pending = this._pendingRPCReplies[msgId]; + delete this._pendingRPCReplies[msgId]; pending.resolveErr(errors.canceled()); }); } @@ -475,15 +476,16 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { const callId = String(req); const result = new LazyPromise(); + const disposable = new DisposableStore(); if (cancellationToken) { - cancellationToken.onCancellationRequested(() => { + disposable.add(cancellationToken.onCancellationRequested(() => { const msg = MessageIO.serializeCancel(req); this._logger?.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `cancel`); this._protocol.send(MessageIO.serializeCancel(req)); - }); + })); } - this._pendingRPCReplies[callId] = result; + this._pendingRPCReplies[callId] = new PendingRPCReply(result, disposable); this._onWillSendRequest(req); const msg = MessageIO.serializeRequest(req, rpcId, methodName, serializedRequestArguments, !!cancellationToken); this._logger?.logOutgoing(msg.byteLength, req, RequestInitiator.LocalSide, `request: ${getStringIdentifierForProxy(rpcId)}.${methodName}(`, args); @@ -492,6 +494,23 @@ export class RPCProtocol extends Disposable implements IRPCProtocol { } } +class PendingRPCReply { + constructor( + private readonly _promise: LazyPromise, + private readonly _disposable: IDisposable + ) { } + + public resolveOk(value: any): void { + this._promise.resolveOk(value); + this._disposable.dispose(); + } + + public resolveErr(err: any): void { + this._promise.resolveErr(err); + this._disposable.dispose(); + } +} + class MessageBuffer { public static alloc(type: MessageType, req: number, messageSize: number): MessageBuffer { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index 0f604a3c481..d63228db9d2 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -97,7 +97,8 @@ export class NativeExtensionService extends AbstractExtensionService implements extensionEnablementService, configurationService, remoteAgentService, - remoteAuthorityResolverService + remoteAuthorityResolverService, + logService ); super( extensionsProposedApi, @@ -435,6 +436,9 @@ export class NativeExtensionService extends AbstractExtensionService implements if (parseExtensionDevOptions(this._environmentService).isExtensionDevTestFromCli) { // When CLI testing make sure to exit with proper exit code + if (isCI) { + this._logService.info(`Asking native host service to exit with code ${code}.`); + } this._nativeHostService.exit(code); } else { // Expected development extension termination: When the extension host goes down we also shutdown the window @@ -463,7 +467,7 @@ export class NativeExtensionService extends AbstractExtensionService implements const allExtensions = await this._scanAllLocalExtensions(); const extension = allExtensions.filter(e => e.identifier.value === resolverExtensionId)[0]; if (extension) { - if (!extensionIsEnabled(this._extensionEnablementService, extension, false)) { + if (!extensionIsEnabled(this._logService, this._extensionEnablementService, extension, false)) { const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window.\nOK to enable?", recommendation.friendlyName); this._notificationService.prompt(Severity.Info, message, [{ @@ -524,6 +528,7 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { @IConfigurationService configurationService: IConfigurationService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @ILogService private readonly _logService: ILogService, ) { this._webWorkerExtHostEnablement = determineLocalWebWorkerExtHostEnablement(environmentService, configurationService); } @@ -564,9 +569,22 @@ class NativeExtensionHostFactory implements IExtensionHostFactory { getInitData: async (): Promise => { if (isInitialStart) { // Here we load even extensions that would be disabled by workspace trust - const localExtensions = checkEnabledAndProposedAPI(this._extensionEnablementService, this._extensionsProposedApi, await this._extensionScanner.scannedExtensions, /* ignore workspace trust */true); + const scannedExtensions = await this._extensionScanner.scannedExtensions; + if (isCI) { + this._logService.info(`NativeExtensionHostFactory._createLocalProcessExtensionHostDataProvider.scannedExtensions: ${scannedExtensions.map(ext => ext.identifier.value).join(',')}`); + } + + const localExtensions = checkEnabledAndProposedAPI(this._logService, this._extensionEnablementService, this._extensionsProposedApi, scannedExtensions, /* ignore workspace trust */true); + if (isCI) { + this._logService.info(`NativeExtensionHostFactory._createLocalProcessExtensionHostDataProvider.localExtensions: ${localExtensions.map(ext => ext.identifier.value).join(',')}`); + } + const runningLocation = runningLocations.computeRunningLocation(localExtensions, [], false); const myExtensions = filterExtensionDescriptions(localExtensions, runningLocation, extRunningLocation => desiredRunningLocation.equals(extRunningLocation)); + if (isCI) { + this._logService.info(`NativeExtensionHostFactory._createLocalProcessExtensionHostDataProvider.myExtensions: ${myExtensions.map(ext => ext.identifier.value).join(',')}`); + } + return { allExtensions: localExtensions, myExtensions: myExtensions.map(extension => extension.identifier) 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 f506bbee811..7bb517785af 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mock } from 'vs/base/test/common/mock'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; @@ -49,6 +50,9 @@ import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemo import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; suite('BrowserExtensionService', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + test('pickRunningLocation', () => { assert.deepStrictEqual(BrowserExtensionHostKindPicker.pickRunningLocation([], false, false, ExtensionRunningPreference.None), null); assert.deepStrictEqual(BrowserExtensionHostKindPicker.pickRunningLocation([], false, true, ExtensionRunningPreference.None), null); @@ -222,6 +226,7 @@ suite('ExtensionService', () => { setup(() => { disposables = new DisposableStore(); + const testProductService = { _serviceBrand: undefined, ...product }; disposables.add(instantiationService = createServices(disposables, [ // custom [IExtensionService, MyTestExtensionService], @@ -235,7 +240,7 @@ suite('ExtensionService', () => { [IExtensionManifestPropertiesService, ExtensionManifestPropertiesService], [IConfigurationService, TestConfigurationService], [IWorkspaceContextService, TestContextService], - [IProductService, { _serviceBrand: undefined, ...product }], + [IProductService, testProductService], [IFileService, TestFileService], [IWorkbenchExtensionEnablementService, TestWorkbenchExtensionEnablementService], [ITelemetryService, NullTelemetryService], @@ -245,7 +250,7 @@ suite('ExtensionService', () => { [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], - [IRemoteAuthorityResolverService, RemoteAuthorityResolverService] + [IRemoteAuthorityResolverService, new RemoteAuthorityResolverService(false, undefined, undefined, testProductService, new NullLogService())] ])); extService = instantiationService.get(IExtensionService); }); @@ -254,6 +259,8 @@ suite('ExtensionService', () => { disposables.dispose(); }); + ensureNoDisposablesAreLeakedInTestSuite(); + test('issue #152204: Remote extension host not disposed after closing vscode client', async () => { await extService.startExtensionHosts(); await extService.stopExtensionHosts('foo'); @@ -269,8 +276,8 @@ suite('ExtensionService', () => { test('Extension host not disposed when vetoed (sync)', async () => { await extService.startExtensionHosts(); - extService.onWillStop(e => e.veto(true, 'test 1')); - extService.onWillStop(e => e.veto(false, 'test 2')); + disposables.add(extService.onWillStop(e => e.veto(true, 'test 1'))); + disposables.add(extService.onWillStop(e => e.veto(false, 'test 2'))); await extService.stopExtensionHosts('foo'); assert.deepStrictEqual(extService.order, (['create 1', 'create 2', 'create 3'])); @@ -279,9 +286,9 @@ suite('ExtensionService', () => { test('Extension host not disposed when vetoed (async)', async () => { await extService.startExtensionHosts(); - extService.onWillStop(e => e.veto(false, 'test 1')); - extService.onWillStop(e => e.veto(Promise.resolve(true), 'test 2')); - extService.onWillStop(e => e.veto(Promise.resolve(false), 'test 3')); + disposables.add(extService.onWillStop(e => e.veto(false, 'test 1'))); + disposables.add(extService.onWillStop(e => e.veto(Promise.resolve(true), 'test 2'))); + disposables.add(extService.onWillStop(e => e.veto(Promise.resolve(false), 'test 3'))); await extService.stopExtensionHosts('foo'); assert.deepStrictEqual(extService.order, (['create 1', 'create 2', 'create 3'])); 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 33a5ed27bdb..52dc0e6997f 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -23,10 +22,11 @@ import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/u import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ExtensionStorageMigration', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); const workspaceStorageHome = joinPath(ROOT, 'workspaceStorageHome'); @@ -36,17 +36,15 @@ suite('ExtensionStorageMigration', () => { instantiationService = workbenchInstantiationService(undefined, disposables); const fileService = disposables.add(new FileService(new NullLogService())); - fileService.registerProvider(ROOT.scheme, disposables.add(new InMemoryFileSystemProvider())); + disposables.add(fileService.registerProvider(ROOT.scheme, disposables.add(new InMemoryFileSystemProvider()))); instantiationService.stub(IFileService, fileService); const environmentService = instantiationService.stub(IEnvironmentService, >{ userRoamingDataHome: ROOT, workspaceStorageHome, cacheHome: ROOT }); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), new NullLogService())); - instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, disposables.add(new UserDataProfilesService(environmentService, fileService, disposables.add(new UriIdentityService(fileService)), new NullLogService()))); + instantiationService.stub(IUserDataProfileService, disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile))); - instantiationService.stub(IExtensionStorageService, instantiationService.createInstance(ExtensionStorageService)); + instantiationService.stub(IExtensionStorageService, disposables.add(instantiationService.createInstance(ExtensionStorageService))); }); - teardown(() => disposables.clear()); - test('migrate extension storage', async () => { const fromExtensionId = 'pub.from', toExtensionId = 'pub.to', storageMigratedKey = `extensionStorage.migrate.${fromExtensionId}-${toExtensionId}`; const extensionStorageService = instantiationService.get(IExtensionStorageService), fileService = instantiationService.get(IFileService), storageService = instantiationService.get(IStorageService), userDataProfilesService = instantiationService.get(IUserDataProfilesService); diff --git a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts index 1924d01fe44..9cde0946333 100644 --- a/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts +++ b/src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts @@ -4,20 +4,34 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IExtensionManifest, ExtensionUntrustedWorkspaceSupportType } from 'vs/platform/extensions/common/extensions'; -import { ExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { TestProductService, TestWorkspaceTrustEnablementService } from 'vs/workbench/test/common/workbenchTestServices'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProductService } from 'vs/platform/product/common/productService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { ExtensionUntrustedWorkspaceSupportType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { NullLogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IWorkspaceTrustEnablementService } from 'vs/platform/workspace/common/workspaceTrust'; +import { ExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { TestProductService, TestWorkspaceTrustEnablementService } from 'vs/workbench/test/common/workbenchTestServices'; suite('ExtensionManifestPropertiesService - ExtensionKind', () => { - let testObject = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService()); + let disposables: DisposableStore; + let testObject: ExtensionManifestPropertiesService; + + setup(() => { + disposables = new DisposableStore(); + testObject = disposables.add(new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService(), new TestWorkspaceTrustEnablementService(), new NullLogService())); + }); + + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); test('declarative with extension dependencies', () => { assert.deepStrictEqual(testObject.getExtensionKind({ extensionDependencies: ['ext1'] }), isWeb ? ['workspace', 'web'] : ['workspace']); @@ -68,12 +82,12 @@ suite('ExtensionManifestPropertiesService - ExtensionKind', () => { }); test('opt out from web through settings even if it can run in web', () => { - testObject = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService({ remote: { extensionKind: { 'pub.a': ['-web'] } } }), new TestWorkspaceTrustEnablementService(), new NullLogService()); + testObject = disposables.add(new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService({ remote: { extensionKind: { 'pub.a': ['-web'] } } }), new TestWorkspaceTrustEnablementService(), new NullLogService())); assert.deepStrictEqual(testObject.getExtensionKind({ browser: 'main.browser.js', publisher: 'pub', name: 'a' }), ['ui', 'workspace']); }); test('opt out from web and include only workspace through settings even if it can run in web', () => { - testObject = new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService({ remote: { extensionKind: { 'pub.a': ['-web', 'workspace'] } } }), new TestWorkspaceTrustEnablementService(), new NullLogService()); + testObject = disposables.add(new ExtensionManifestPropertiesService(TestProductService, new TestConfigurationService({ remote: { extensionKind: { 'pub.a': ['-web', 'workspace'] } } }), new TestWorkspaceTrustEnablementService(), new NullLogService())); assert.deepStrictEqual(testObject.getExtensionKind({ browser: 'main.browser.js', publisher: 'pub', name: 'a' }), ['workspace']); }); diff --git a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts index 7724cb3d3c3..e351f2485fe 100644 --- a/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { ProxyIdentifier, SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { VSBuffer } from 'vs/base/common/buffer'; suite('RPCProtocol', () => { + let disposables: DisposableStore; + class MessagePassingProtocol implements IMessagePassingProtocol { private _pair?: MessagePassingProtocol; @@ -39,13 +43,15 @@ suite('RPCProtocol', () => { } setup(() => { + disposables = new DisposableStore(); + const a_protocol = new MessagePassingProtocol(); const b_protocol = new MessagePassingProtocol(); a_protocol.setPair(b_protocol); b_protocol.setPair(a_protocol); - const A = new RPCProtocol(a_protocol); - const B = new RPCProtocol(b_protocol); + const A = disposables.add(new RPCProtocol(a_protocol)); + const B = disposables.add(new RPCProtocol(b_protocol)); const bIdentifier = new ProxyIdentifier('bb'); const bInstance = new BClass(); @@ -53,6 +59,12 @@ suite('RPCProtocol', () => { bProxy = A.getProxy(bIdentifier); }); + teardown(() => { + disposables.dispose(); + }); + + ensureNoDisposablesAreLeakedInTestSuite(); + test('simple call', function (done) { delegate = (a1: number, a2: number) => a1 + a2; bProxy.$m(4, 1).then((res: number) => { @@ -130,7 +142,8 @@ suite('RPCProtocol', () => { // this is an implementation which, when cancellation is triggered, will return 7 delegate = (a1: number, token: CancellationToken) => { return new Promise((resolve, reject) => { - token.onCancellationRequested((e) => { + const disposable = token.onCancellationRequested((e) => { + disposable.dispose(); resolve(7); }); }); diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts index fe3579726b1..63bf77e5e15 100644 --- a/src/vs/workbench/services/history/browser/historyService.ts +++ b/src/vs/workbench/services/history/browser/historyService.ts @@ -1115,6 +1115,20 @@ export class HistoryService extends Disposable implements IHistoryService { } //#endregion + + override dispose(): void { + super.dispose(); + + for (const [, stack] of this.editorGroupScopedNavigationStacks) { + stack.disposable.dispose(); + } + + for (const [, editors] of this.editorScopedNavigationStacks) { + for (const [, stack] of editors) { + stack.disposable.dispose(); + } + } + } } registerSingleton(IHistoryService, HistoryService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/history/test/browser/historyService.test.ts b/src/vs/workbench/services/history/test/browser/historyService.test.ts index a83256a6879..d4c6b88601a 100644 --- a/src/vs/workbench/services/history/test/browser/historyService.test.ts +++ b/src/vs/workbench/services/history/test/browser/historyService.test.ts @@ -88,7 +88,7 @@ suite('HistoryService', function () { assert.strictEqual(part.activeGroup.activeEditor, input2); }); - test.skip('back / forward: is editor group aware', async function () { // todo@bpasero + test('back / forward: is editor group aware', async function () { const [part, historyService, editorService, , instantiationService] = await createServices(); const resource: URI = toResource.call(this, '/path/index.txt'); @@ -297,7 +297,7 @@ suite('HistoryService', function () { assert.strictEqual(options.selection?.endColumn, expected.endColumn); } - test.skip('back / forward: tracks editor moves across groups', async function () { // TODO@bpasero + test('back / forward: tracks editor moves across groups', async function () { const [part, historyService, editorService, , instantiationService] = await createServices(); const resource1: URI = toResource.call(this, '/path/one.txt'); @@ -328,7 +328,7 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test.skip('back / forward: tracks group removals', async function () { // TODO@bpasero + test('back / forward: tracks group removals', async function () { const [part, historyService, editorService, , instantiationService] = await createServices(); const resource1 = toResource.call(this, '/path/one.txt'); @@ -510,7 +510,7 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test.skip('back / forward: editor group scope', async function () { // TODO@bpasero + test('back / forward: editor group scope', async function () { const [part, historyService, editorService, , instantiationService] = await createServices(GoScope.EDITOR_GROUP); const resource1 = toResource.call(this, '/path/one.txt'); @@ -734,7 +734,7 @@ suite('HistoryService', function () { return workbenchTeardown(instantiationService); }); - test.skip('open next/previous recently used editor (multi group)', async () => { // TODO@bpasero + test('open next/previous recently used editor (multi group)', async () => { const [part, historyService, editorService, , instantiationService] = await createServices(); const rootGroup = part.activeGroup; 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 e60d05855d9..34591dbfc35 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -26,12 +26,12 @@ import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataPr import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { joinPath } from 'vs/base/common/resources'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface Modifiers { metaKey?: boolean; @@ -44,7 +44,7 @@ const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); suite('KeybindingsEditing', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; let fileService: IFileService; let environmentService: IEnvironmentService; @@ -59,7 +59,6 @@ suite('KeybindingsEditing', () => { fileService = disposables.add(new FileService(logService)); const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); - disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())))); const userFolder = joinPath(ROOT, 'User'); await fileService.createFolder(userFolder); @@ -67,8 +66,10 @@ suite('KeybindingsEditing', () => { const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'eol': '\n' }); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService); - userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + const uriIdentityService = disposables.add(new UriIdentityService(fileService)); + const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); + userDataProfileService = disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile)); + disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService())))); instantiationService = workbenchInstantiationService({ fileService: () => fileService, @@ -79,8 +80,6 @@ suite('KeybindingsEditing', () => { testObject = disposables.add(instantiationService.createInstance(KeybindingsEditingService)); }); - teardown(() => disposables.clear()); - test('errors cases - parse errors', async () => { await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(',,,,,,,,,,,,,,')); try { diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index 1e5631e8a67..58c5d5169c3 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -20,6 +20,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/preferences'; import { Action2, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; interface Modifiers { metaKey?: boolean; @@ -30,26 +31,23 @@ interface Modifiers { suite('KeybindingsEditorModel', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; let testObject: KeybindingsEditorModel; let extensions: Partial[] = []; setup(() => { extensions = []; - instantiationService = new TestInstantiationService(); + instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(IKeybindingService, {}); instantiationService.stub(IExtensionService, >{ whenInstalledExtensionsRegistered: () => Promise.resolve(true), get extensions() { return extensions; } }); - testObject = instantiationService.createInstance(KeybindingsEditorModel, OS); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OS)); - CommandsRegistry.registerCommand('command_without_keybinding', () => { }); - }); - - teardown(() => { - instantiationService.dispose(); + disposables.add(CommandsRegistry.registerCommand('command_without_keybinding', () => { })); }); test('fetch returns default keybindings', async () => { @@ -190,7 +188,7 @@ suite('KeybindingsEditorModel', () => { }); test('convert without title and binding to entry', async () => { - CommandsRegistry.registerCommand('command_without_keybinding', () => { }); + disposables.add(CommandsRegistry.registerCommand('command_without_keybinding', () => { })); prepareKeybindingService(); await testObject.resolve(new Map()); @@ -310,7 +308,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by cmd key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); @@ -324,7 +322,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by meta key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); @@ -338,7 +336,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by command key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); @@ -352,7 +350,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by windows key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Windows); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Windows)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); @@ -460,7 +458,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by modifiers and key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { altKey: true, metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -473,7 +471,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by modifiers in random order and key', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -486,7 +484,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter by first part', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.Delete }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -499,7 +497,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter matches in chord part', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.Delete }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -512,7 +510,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter matches first part and in chord part', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.Delete }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.UpArrow }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -549,7 +547,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter exact matches with first and chord part no results', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.Delete, modifiers: { metaKey: true } }, when: 'whenContext1 && whenContext2', isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { shiftKey: true, metaKey: true } }, secondChord: { keyCode: KeyCode.UpArrow }, when: 'whenContext1 && whenContext2', isDefault: false })); @@ -618,7 +616,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter exact matches with user settings label', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = 'a' + uuid.generateUuid(); const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.DownArrow } }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command: 'down', firstChord: { keyCode: KeyCode.Escape } })); @@ -642,7 +640,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter modifiers are not matched when not completely matched (prefix)', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const term = `alt.${uuid.generateUuid()}`; const command = `command.${term}`; const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape }, isDefault: false }); @@ -656,7 +654,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter modifiers are not matched when not completely matched (includes)', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const term = `abcaltdef.${uuid.generateUuid()}`; const command = `command.${term}`; const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape }, isDefault: false }); @@ -670,7 +668,7 @@ suite('KeybindingsEditorModel', () => { }); test('filter modifiers are matched with complete term', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command = `command.${uuid.generateUuid()}`; const expected = aResolvedKeybindingItem({ command, firstChord: { keyCode: KeyCode.Escape, modifiers: { altKey: true } }, isDefault: false }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command: 'some_command', firstChord: { keyCode: KeyCode.Escape }, isDefault: false })); @@ -682,11 +680,11 @@ suite('KeybindingsEditorModel', () => { }); test('filter by extension', async () => { - testObject = instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh); + testObject = disposables.add(instantiationService.createInstance(KeybindingsEditorModel, OperatingSystem.Macintosh)); const command1 = `command.${uuid.generateUuid()}`; const command2 = `command.${uuid.generateUuid()}`; extensions.push({ identifier: new ExtensionIdentifier('foo'), displayName: 'foo bar' }, { identifier: new ExtensionIdentifier('bar'), displayName: 'bar foo' }); - MenuRegistry.addCommand({ id: command2, title: 'title', category: 'category', source: { id: extensions[1].identifier!.value, title: extensions[1].displayName! } }); + disposables.add(MenuRegistry.addCommand({ id: command2, title: 'title', category: 'category', source: { id: extensions[1].identifier!.value, title: extensions[1].displayName! } })); const expected = aResolvedKeybindingItem({ command: command1, firstChord: { keyCode: KeyCode.Escape, modifiers: { altKey: true } }, isDefault: true, extensionId: extensions[0].identifier!.value }); prepareKeybindingService(expected, aResolvedKeybindingItem({ command: command2, isDefault: true })); @@ -707,7 +705,7 @@ suite('KeybindingsEditorModel', () => { } function registerCommandWithTitle(command: string, title: string): void { - registerAction2(class extends Action2 { + disposables.add(registerAction2(class extends Action2 { constructor() { super({ id: command, @@ -716,7 +714,7 @@ suite('KeybindingsEditorModel', () => { }); } async run(): Promise { } - }); + })); } function assertKeybindingItems(actual: ResolvedKeybindingItem[], expected: ResolvedKeybindingItem[]) { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 6935850b671..46189842db4 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -26,6 +26,7 @@ export { TextSearchCompleteMessageType }; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.panel.search'; export const VIEW_ID = 'workbench.view.search'; +export const SEARCH_RESULT_LANGUAGE_ID = 'search-result'; export const SEARCH_EXCLUDE_CONFIG = 'search.exclude'; diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index bbabce133c8..4e8fb9a5805 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -21,7 +21,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; +import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class SearchService extends Disposable implements ISearchService { @@ -462,7 +462,7 @@ export class SearchService extends Disposable implements ISearchService { } // Skip search results - if (model.getLanguageId() === 'search-result' && !(query.includePattern && query.includePattern['**/*.code-search'])) { + if (model.getLanguageId() === SEARCH_RESULT_LANGUAGE_ID && !(query.includePattern && query.includePattern['**/*.code-search'])) { // TODO: untitled search editors will be excluded from search even when include *.code-search is specified return; } diff --git a/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts b/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts index 70ae34239f7..700329e7361 100644 --- a/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts +++ b/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts @@ -31,8 +31,7 @@ export class NativeSecretStorageService extends BaseSecretStorageService { @ILogService logService: ILogService ) { super( - // TODO: rename disableKeytar to disableSecretStorage or similar - !!_environmentService.disableKeytar, + !!_environmentService.useInMemorySecretStorage, storageService, encryptionService, logService @@ -43,7 +42,7 @@ export class NativeSecretStorageService extends BaseSecretStorageService { this._sequencer.queue(key, async () => { await this.resolvedStorageService; - if (this.type !== 'persisted' && !this._environmentService.disableKeytar) { + if (this.type !== 'persisted' && !this._environmentService.useInMemorySecretStorage) { this._logService.trace('[NativeSecretStorageService] Notifying user that secrets are not being stored on disk.'); await this.notifyOfNoEncryptionOnce(); } 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 25472109405..953f54c86d8 100644 --- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts +++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts @@ -17,11 +17,9 @@ 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 { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { IUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile } 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(); @@ -50,7 +48,7 @@ async function createStorageService(): Promise<[DisposableStore, BrowserStorageS cacheHome: joinPath(inMemoryExtraProfileRoot, 'cache') }; - const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, disposables.add(new UserDataProfileService(inMemoryExtraProfile, new UserDataProfilesService(TestEnvironmentService, fileService, disposables.add(new UriIdentityService(fileService)), logService))), logService)); + const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, disposables.add(new UserDataProfileService(inMemoryExtraProfile)), logService)); await storageService.initialize(); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index a60dc9a4560..4ae7f6692c6 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -168,7 +168,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // exists (network shares issue: https://github.com/microsoft/vscode/issues/13665). // Since we do not want to mark the model as orphaned, we have to check if the // file is really gone and not just a faulty file event. - await timeout(100); + await timeout(100, CancellationToken.None); if (this.isDisposed()) { newInOrphanModeValidated = true; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.integrationTest.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.integrationTest.ts index 4f4d43cf31c..866265c14fa 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.integrationTest.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.integrationTest.ts @@ -7,32 +7,32 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; -import { toResource } from 'vs/base/test/common/utils'; +import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; suite('Files - TextFileEditorModel (integration)', () => { - let disposables: DisposableStore; + const disposables = new DisposableStore(); + let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; let content: string; setup(() => { - disposables = new DisposableStore(); instantiationService = workbenchInstantiationService(undefined, disposables); accessor = instantiationService.createInstance(TestServiceAccessor); content = accessor.fileService.getContent(); + disposables.add(toDisposable(() => accessor.fileService.setContent(content))); + disposables.add(accessor.textFileService.files); }); teardown(() => { - (accessor.textFileService.files).dispose(); - accessor.fileService.setContent(content); - disposables.dispose(); + disposables.clear(); }); test('backup and restore (simple)', async function () { @@ -45,7 +45,7 @@ suite('Files - TextFileEditorModel (integration)', () => { }); async function testBackupAndRestore(resourceA: URI, resourceB: URI, contents: string): Promise { - const originalModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resourceA, 'utf8', undefined); + const originalModel: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, resourceA, 'utf8', undefined)); await originalModel.resolve({ contents: await createTextBufferFactoryFromStream(await accessor.textFileService.getDecodedStream(resourceA, bufferToStream(VSBuffer.fromString(contents)))) }); @@ -56,13 +56,12 @@ suite('Files - TextFileEditorModel (integration)', () => { const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: resourceB }; await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); - const modelRestored: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, modelRestoredIdentifier.resource, 'utf8', undefined); + const modelRestored: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, modelRestoredIdentifier.resource, 'utf8', undefined)); await modelRestored.resolve(); assert.strictEqual(modelRestored.textEditorModel?.getValue(), contents); assert.strictEqual(modelRestored.isDirty(), true); - - originalModel.dispose(); - modelRestored.dispose(); } + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index d1a3df96cd2..8528d367687 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -8,7 +8,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { EncodingMode, TextFileEditorModelState, snapshotToString, isTextFileEditorModel, ITextFileEditorModelSaveEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel, getLastResolvedFileStat } from 'vs/workbench/test/browser/workbenchTestServices'; -import { toResource } from 'vs/base/test/common/utils'; +import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { DeferredPromise, timeout } from 'vs/base/common/async'; @@ -42,6 +42,10 @@ suite('Files - TextFileEditorModel', () => { }); teardown(async () => { + for (const textFileEditorModel of accessor.textFileService.files.models) { + textFileEditorModel.dispose(); + } + disposables.clear(); }); @@ -282,7 +286,7 @@ suite('Files - TextFileEditorModel', () => { }); test('setEncoding - encode', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); let encodingEvent = false; disposables.add(model.onDidChangeEncoding(() => encodingEvent = true)); @@ -297,12 +301,10 @@ suite('Files - TextFileEditorModel', () => { assert.ok(encodingEvent); assert.ok(getLastModifiedTime(model) <= Date.now()); // indicates model was saved due to encoding change - - model.dispose(); }); test('setEncoding - decode', async function () { - let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + let model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); accessor.workingCopyService.testUnregisterWorkingCopy(model); // causes issues with subsequent resolves otherwise await model.setEncoding('utf16', EncodingMode.Decode); @@ -313,11 +315,10 @@ suite('Files - TextFileEditorModel', () => { model = accessor.workingCopyService.get(model) as TextFileEditorModel; assert.ok(model.isResolved()); // model got resolved due to decoding - model.dispose(); }); test('setEncoding - decode dirty file saves first', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); accessor.workingCopyService.testUnregisterWorkingCopy(model); // causes issues with subsequent resolves otherwise await model.resolve(); @@ -328,18 +329,17 @@ suite('Files - TextFileEditorModel', () => { await model.setEncoding('utf16', EncodingMode.Decode); assert.strictEqual(model.isDirty(), false); - model.dispose(); }); test('encoding updates with language based configuration', async function () { const languageId = 'text-file-model-test'; - const registration = accessor.languageService.registerLanguage({ + disposables.add(accessor.languageService.registerLanguage({ id: languageId, - }); + })); accessor.testConfigurationService.setOverrideIdentifiers('files.encoding', [languageId]); - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); accessor.workingCopyService.testUnregisterWorkingCopy(model); // causes issues with subsequent resolves otherwise await model.resolve(); @@ -348,11 +348,11 @@ suite('Files - TextFileEditorModel', () => { // We use this listener as a way to figure out that the working // copy was resolved again as part of the language change - const listener = accessor.workingCopyService.onDidRegister(e => { + disposables.add(accessor.workingCopyService.onDidRegister(e => { if (isEqual(e.resource, model.resource)) { deferredPromise.complete(model as TextFileEditorModel); } - }); + })); accessor.testConfigurationService.setUserConfiguration('files.encoding', UTF16be); @@ -361,17 +361,13 @@ suite('Files - TextFileEditorModel', () => { await deferredPromise.p; assert.strictEqual(model.getEncoding(), UTF16be); - - model.dispose(); - listener.dispose(); - registration.dispose(); }); test('create with language', async function () { const languageId = 'text-file-model-test'; - const registration = accessor.languageService.registerLanguage({ + disposables.add(accessor.languageService.registerLanguage({ id: languageId, - }); + })); const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', languageId); @@ -381,8 +377,6 @@ suite('Files - TextFileEditorModel', () => { model.dispose(); assert.ok(!accessor.modelService.getModel(model.resource)); - - registration.dispose(); }); test('disposes when underlying model is destroyed', async function () { @@ -408,7 +402,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Resolve returns dirty model as long as model is dirty', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); @@ -417,7 +411,6 @@ suite('Files - TextFileEditorModel', () => { await model.resolve(); assert.ok(model.isDirty()); - model.dispose(); }); test('Resolve with contents', async function () { @@ -480,8 +473,6 @@ suite('Files - TextFileEditorModel', () => { assert.ok(workingCopyEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), false); - - model.dispose(); }); test('Revert (soft)', async function () { @@ -515,12 +506,10 @@ suite('Files - TextFileEditorModel', () => { assert.ok(workingCopyEvent); assert.strictEqual(accessor.workingCopyService.dirtyCount, 0); assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), false); - - model.dispose(); }); test('Undo to saved state turns model non-dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('Hello Text')); assert.ok(model.isDirty()); @@ -530,7 +519,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Resolve and undo turns model dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await model.resolve(); accessor.fileService.setContent('Hello Change'); @@ -545,7 +534,7 @@ suite('Files - TextFileEditorModel', () => { test('Update Dirty', async function () { let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); model.setDirty(true); assert.ok(!model.isDirty()); // needs to be resolved @@ -574,8 +563,6 @@ suite('Files - TextFileEditorModel', () => { model.setDirty(false); assert.strictEqual(model.isDirty(), false); assert.strictEqual(eventCounter, 2); - - model.dispose(); }); test('No Dirty or saving for readonly models', async function () { @@ -586,7 +573,7 @@ suite('Files - TextFileEditorModel', () => { } })); - const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model = disposables.add(instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); let saveEvent = false; disposables.add(model.onDidSave(() => { @@ -604,12 +591,10 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!model.isDirty()); assert.ok(!workingCopyEvent); - - model.dispose(); }); test('File not modified error is handled gracefully', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await model.resolve(); @@ -620,26 +605,24 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model); assert.strictEqual(getLastModifiedTime(model), mtime); - model.dispose(); }); test('Resolve error is handled gracefully if model already exists', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await model.resolve(); accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND)); await model.resolve(); assert.ok(model); - model.dispose(); }); test('save() and isDirty() - proper with check for mtimes', async function () { const input1 = disposables.add(createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async2.txt'))); const input2 = disposables.add(createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async.txt'))); - const model1 = await input1.resolve() as TextFileEditorModel; - const model2 = await input2.resolve() as TextFileEditorModel; + const model1 = disposables.add(await input1.resolve() as TextFileEditorModel); + const model2 = disposables.add(await input2.resolve() as TextFileEditorModel); model1.updateTextEditorModel(createTextBufferFactory('foo')); @@ -671,14 +654,11 @@ suite('Files - TextFileEditorModel', () => { assert.ok(assertIsDefined(getLastResolvedFileStat(model1)).mtime > m1Mtime); assert.ok(assertIsDefined(getLastResolvedFileStat(model2)).mtime > m2Mtime); } - - model1.dispose(); - model2.dispose(); }); test('Save Participant', async function () { let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); disposables.add(model.onDidSave(() => { assert.strictEqual(snapshotToString(model.createSnapshot()!), eventCounter === 1 ? 'bar' : 'foobar'); @@ -708,40 +688,35 @@ suite('Files - TextFileEditorModel', () => { await model.save(); assert.strictEqual(eventCounter, 3); - - model.dispose(); }); test('Save Participant - skip', async function () { let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); - const participant = accessor.textFileService.files.addSaveParticipant({ + disposables.add(accessor.textFileService.files.addSaveParticipant({ participate: async () => { eventCounter++; } - }); + })); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); await model.save({ skipSaveParticipants: true }); assert.strictEqual(eventCounter, 0); - - participant.dispose(); - model.dispose(); }); test('Save Participant, async participant', async function () { let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); disposables.add(model.onDidSave(() => { assert.ok(!model.isDirty()); eventCounter++; })); - const participant = accessor.textFileService.files.addSaveParticipant({ + disposables.add(accessor.textFileService.files.addSaveParticipant({ participate: model => { assert.ok(model.isDirty()); (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar')); @@ -750,7 +725,7 @@ suite('Files - TextFileEditorModel', () => { return timeout(10); } - }); + })); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); @@ -759,35 +734,29 @@ suite('Files - TextFileEditorModel', () => { await model.save(); assert.strictEqual(eventCounter, 2); assert.ok(Date.now() - now >= 10); - - model.dispose(); - participant.dispose(); }); test('Save Participant, bad participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); - const participant = accessor.textFileService.files.addSaveParticipant({ + disposables.add(accessor.textFileService.files.addSaveParticipant({ participate: async () => { new Error('boom'); } - }); + })); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); await model.save(); - - model.dispose(); - participant.dispose(); }); test('Save Participant, participant cancelled when saved again', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); const participations: boolean[] = []; - const participant = accessor.textFileService.files.addSaveParticipant({ + disposables.add(accessor.textFileService.files.addSaveParticipant({ participate: async (model, context, progress, token) => { await timeout(10); @@ -795,7 +764,7 @@ suite('Files - TextFileEditorModel', () => { participations.push(true); } } - }); + })); await model.resolve(); @@ -813,54 +782,41 @@ suite('Files - TextFileEditorModel', () => { await Promise.all([p1, p2, p3, p4]); assert.strictEqual(participations.length, 1); - - model.dispose(); - participant.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (sync save, no model change)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await testSaveFromSaveParticipant(model, false, false, false); - - model.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (async save, no model change)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await testSaveFromSaveParticipant(model, true, false, false); - - model.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (sync save, model change)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await testSaveFromSaveParticipant(model, false, true, false); - - model.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (async save, model change)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await testSaveFromSaveParticipant(model, true, true, false); - - model.dispose(); }); test('Save Participant, calling save from within is unsupported but does not explode (force)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined)); await testSaveFromSaveParticipant(model, false, false, true); - - model.dispose(); }); async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean, modelChange: boolean, force: boolean): Promise { - const disposable = accessor.textFileService.files.addSaveParticipant({ + disposables.add(accessor.textFileService.files.addSaveParticipant({ participate: async () => { if (async) { await timeout(10); @@ -884,14 +840,14 @@ suite('Files - TextFileEditorModel', () => { await savePromise; } } - }); + })); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); const savePromise = model.save(force ? { force } : undefined); await savePromise; - - disposable.dispose(); } + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts index 71b9b4c9957..6cbeccc8876 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts @@ -9,12 +9,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { workbenchInstantiationService, TestServiceAccessor, ITestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { toResource } from 'vs/base/test/common/utils'; +import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { timeout } from 'vs/base/common/async'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; suite('Files - TextFileEditorModelManager', () => { @@ -25,6 +25,7 @@ suite('Files - TextFileEditorModelManager', () => { setup(() => { instantiationService = workbenchInstantiationService(undefined, disposables); accessor = instantiationService.createInstance(TestServiceAccessor); + disposables.add(toDisposable(() => accessor.textFileService.files as ITestTextFileEditorModelManager)); }); teardown(() => { @@ -34,9 +35,9 @@ suite('Files - TextFileEditorModelManager', () => { test('add, remove, clear, get, getAll', function () { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); + const model1: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined)); + const model2: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined)); + const model3: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined)); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -80,12 +81,6 @@ suite('Files - TextFileEditorModelManager', () => { manager.dispose(); results = manager.models; assert.strictEqual(0, results.length); - - model1.dispose(); - model2.dispose(); - model3.dispose(); - - manager.dispose(); }); test('resolve', async () => { @@ -94,9 +89,9 @@ suite('Files - TextFileEditorModelManager', () => { const encoding = 'utf8'; const events: ITextFileEditorModel[] = []; - const listener = manager.onDidCreate(model => { + disposables.add(manager.onDidCreate(model => { events.push(model); - }); + })); const modelPromise = manager.resolve(resource, { encoding }); assert.ok(manager.get(resource)); // model known even before resolved() @@ -118,30 +113,22 @@ suite('Files - TextFileEditorModelManager', () => { assert.strictEqual(events.length, 2); assert.strictEqual(events[0].resource.toString(), model1.resource.toString()); assert.strictEqual(events[1].resource.toString(), model2.resource.toString()); - - listener.dispose(); - - model1.dispose(); - model2.dispose(); - model3.dispose(); - - manager.dispose(); }); test('resolve (async)', async () => { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; const resource = URI.file('/path/index.txt'); - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); let didResolve = false; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model.resource.toString() === resource.toString()) { didResolve = true; resolve(); } - }); + })); }); manager.resolve(resource, { reload: { async: true } }); @@ -155,14 +142,14 @@ suite('Files - TextFileEditorModelManager', () => { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; const resource = URI.file('/path/index.txt'); - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); let didResolve = false; - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model.resource.toString() === resource.toString()) { didResolve = true; } - }); + })); await manager.resolve(resource, { reload: { async: false } }); assert.strictEqual(didResolve, true); @@ -176,7 +163,7 @@ suite('Files - TextFileEditorModelManager', () => { let error: Error | undefined = undefined; try { - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); } catch (e) { error = e; } @@ -189,13 +176,13 @@ suite('Files - TextFileEditorModelManager', () => { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; const resource = URI.file('/path/index.txt'); - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('fail', FileOperationResult.FILE_OTHER_ERROR)); let error: Error | undefined = undefined; try { - await manager.resolve(resource, { reload: { async: false } }); + disposables.add(await manager.resolve(resource, { reload: { async: false } })); } catch (e) { error = e; } @@ -208,16 +195,13 @@ suite('Files - TextFileEditorModelManager', () => { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; const resource = URI.file('/test.html'); - const model = await manager.resolve(resource, { contents: createTextBufferFactory('Hello World') }); + const model = disposables.add(await manager.resolve(resource, { contents: createTextBufferFactory('Hello World') })); assert.strictEqual(model.textEditorModel?.getValue(), 'Hello World'); assert.strictEqual(model.isDirty(), true); - await manager.resolve(resource, { contents: createTextBufferFactory('More Changes') }); + disposables.add(await manager.resolve(resource, { contents: createTextBufferFactory('More Changes') })); assert.strictEqual(model.textEditorModel?.getValue(), 'More Changes'); assert.strictEqual(model.isDirty(), true); - - model.dispose(); - manager.dispose(); }); test('multiple resolves execute in sequence', async () => { @@ -227,12 +211,12 @@ suite('Files - TextFileEditorModelManager', () => { let resolvedModel: unknown; const contents: string[] = []; - manager.onDidResolve(e => { + disposables.add(manager.onDidResolve(e => { if (e.model.resource.toString() === resource.toString()) { - resolvedModel = e.model as TextFileEditorModel; + resolvedModel = disposables.add(e.model as TextFileEditorModel); contents.push(e.model.textEditorModel!.getValue()); } - }); + })); await Promise.all([ manager.resolve(resource), @@ -249,17 +233,14 @@ suite('Files - TextFileEditorModelManager', () => { assert.strictEqual(contents[0], 'Hello Html'); assert.strictEqual(contents[1], 'Hello World'); assert.strictEqual(contents[2], 'More Changes'); - - resolvedModel.dispose(); - manager.dispose(); }); test('removed from cache when model disposed', function () { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); + const model1: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined)); + const model2: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined)); + const model3: TextFileEditorModel = disposables.add(instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined)); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -269,11 +250,6 @@ suite('Files - TextFileEditorModelManager', () => { model1.dispose(); assert(!manager.get(URI.file('/test.html'))); - - model2.dispose(); - model3.dispose(); - - manager.dispose(); }); test('events', async function () { @@ -290,19 +266,19 @@ suite('Files - TextFileEditorModelManager', () => { let savedCounter = 0; let encodingCounter = 0; - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model.resource.toString() === resource1.toString()) { resolvedCounter++; } - }); + })); - manager.onDidRemove(resource => { + disposables.add(manager.onDidRemove(resource => { if (resource.toString() === resource1.toString() || resource.toString() === resource2.toString()) { removedCounter++; } - }); + })); - manager.onDidChangeDirty(model => { + disposables.add(manager.onDidChangeDirty(model => { if (model.resource.toString() === resource1.toString()) { if (model.isDirty()) { gotDirtyCounter++; @@ -310,25 +286,25 @@ suite('Files - TextFileEditorModelManager', () => { gotNonDirtyCounter++; } } - }); + })); - manager.onDidRevert(model => { + disposables.add(manager.onDidRevert(model => { if (model.resource.toString() === resource1.toString()) { revertedCounter++; } - }); + })); - manager.onDidSave(({ model }) => { + disposables.add(manager.onDidSave(({ model }) => { if (model.resource.toString() === resource1.toString()) { savedCounter++; } - }); + })); - manager.onDidChangeEncoding(model => { + disposables.add(manager.onDidChangeEncoding(model => { if (model.resource.toString() === resource1.toString()) { encodingCounter++; } - }); + })); const model1 = await manager.resolve(resource1, { encoding: 'utf8' }); assert.strictEqual(resolvedCounter, 1); @@ -361,8 +337,6 @@ suite('Files - TextFileEditorModelManager', () => { model2.dispose(); assert.ok(!accessor.modelService.getModel(resource1)); assert.ok(!accessor.modelService.getModel(resource2)); - - manager.dispose(); }); test('disposing model takes it out of the manager', async function () { @@ -374,7 +348,6 @@ suite('Files - TextFileEditorModelManager', () => { model.dispose(); assert.ok(!manager.get(resource)); assert.ok(!accessor.modelService.getModel(model.resource)); - manager.dispose(); }); test('canDispose with dirty model', async function () { @@ -382,7 +355,7 @@ suite('Files - TextFileEditorModelManager', () => { const resource = toResource.call(this, '/path/index_something.txt'); - const model = await manager.resolve(resource, { encoding: 'utf8' }); + const model = disposables.add(await manager.resolve(resource, { encoding: 'utf8' })); (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('make dirty')); const canDisposePromise = manager.canDispose(model as TextFileEditorModel); @@ -402,46 +375,40 @@ suite('Files - TextFileEditorModelManager', () => { const canDispose2 = manager.canDispose(model as TextFileEditorModel); assert.strictEqual(canDispose2, true); - - manager.dispose(); }); test('language', async function () { const languageId = 'text-file-model-manager-test'; - const registration = accessor.languageService.registerLanguage({ + disposables.add(accessor.languageService.registerLanguage({ id: languageId, - }); + })); const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; - const resource = toResource.call(this, '/path/index_something.txt'); + const resource: URI = toResource.call(this, '/path/index_something.txt'); - let model = await manager.resolve(resource, { languageId: languageId }); + let model = disposables.add(await manager.resolve(resource, { languageId: languageId })); assert.strictEqual(model.textEditorModel!.getLanguageId(), languageId); model = await manager.resolve(resource, { languageId: 'text' }); assert.strictEqual(model.textEditorModel!.getLanguageId(), PLAINTEXT_LANGUAGE_ID); - - model.dispose(); - manager.dispose(); - registration.dispose(); }); test('file change events trigger reload (on a resolved model)', async () => { const manager = accessor.textFileService.files as ITestTextFileEditorModelManager; const resource = URI.file('/path/index.txt'); - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); let didResolve = false; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model.resource.toString() === resource.toString()) { didResolve = true; resolve(); } - }); + })); }); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }], false)); @@ -459,7 +426,8 @@ suite('Files - TextFileEditorModelManager', () => { let didResolve = false; let resolvedCounter = 0; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { + disposables.add(model); if (model.resource.toString() === resource.toString()) { resolvedCounter++; if (resolvedCounter === 2) { @@ -467,7 +435,7 @@ suite('Files - TextFileEditorModelManager', () => { resolve(); } } - }); + })); }); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }], false)); @@ -475,4 +443,6 @@ suite('Files - TextFileEditorModelManager', () => { await onDidResolve; assert.strictEqual(didResolve, true); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index ea58be56b01..e82e03d208a 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -114,7 +114,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, - @IUserDataInitializationService userDataInitializationService: IUserDataInitializationService, + @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, @ILanguageService languageService: ILanguageService ) { this.container = layoutService.container; @@ -180,7 +180,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.applyAndSetProductIconTheme(productIconData, true); } - Promise.all([extensionService.whenInstalledExtensionsRegistered(), userDataInitializationService.whenInitializationFinished()]).then(_ => { + extensionService.whenInstalledExtensionsRegistered().then(_ => { this.installConfigurationListener(); this.installPreferredSchemeListener(); this.installRegistryListeners(); @@ -209,17 +209,23 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; - const theme = this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, fallbackTheme); const preferredColorScheme = this.getPreferredColorScheme(); const prevScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, PERSISTED_OS_COLOR_SCHEME_SCOPE); if (preferredColorScheme !== prevScheme) { this.storageService.store(PERSISTED_OS_COLOR_SCHEME, preferredColorScheme, PERSISTED_OS_COLOR_SCHEME_SCOPE, StorageTarget.USER); - if (preferredColorScheme && theme?.type !== preferredColorScheme) { + if (preferredColorScheme && this.currentColorTheme.type !== preferredColorScheme) { return this.applyPreferredColorTheme(preferredColorScheme); } } + let theme = this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, undefined); + if (!theme) { + // If the current theme is not available, first make sure setting sync is complete + await this.userDataInitializationService.whenInitializationFinished(); + // try to get the theme again, now with a fallback to the default themes + const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; + theme = this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, fallbackTheme); + } return this.setColorTheme(theme && theme.id, undefined); }; @@ -228,7 +234,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const theme = this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme); + let theme = this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme); + if (!theme) { + // If the current theme is not available, first make sure setting sync is complete + await this.userDataInitializationService.whenInitializationFinished(); + theme = this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme); + } return this.setFileIconTheme(theme ? theme.id : DEFAULT_FILE_ICON_THEME_ID, undefined); }; @@ -237,7 +248,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setProductIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const theme = this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme); + let theme = this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme); + if (!theme) { + // If the current theme is not available, first make sure setting sync is complete + await this.userDataInitializationService.whenInitializationFinished(); + theme = this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme); + } return this.setProductIconTheme(theme ? theme.id : DEFAULT_PRODUCT_ICON_THEME_ID, undefined); }; diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts index 828a72f1d0d..f64d8c7e4c8 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts @@ -5,27 +5,27 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Untitled text editors', () => { - let disposables: DisposableStore; + const disposables = new DisposableStore(); + let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; setup(() => { - disposables = new DisposableStore(); instantiationService = workbenchInstantiationService(undefined, disposables); accessor = instantiationService.createInstance(TestServiceAccessor); + disposables.add(accessor.untitledTextEditorService); }); teardown(() => { - (accessor.untitledTextEditorService as UntitledTextEditorService).dispose(); - disposables.dispose(); + disposables.clear(); }); test('backup and restore (simple)', async function () { @@ -39,24 +39,21 @@ suite('Untitled text editors', () => { async function testBackupAndRestore(content: string) { const service = accessor.untitledTextEditorService; - const originalInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - const restoredInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + const originalInput = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, service.create())); + const restoredInput = disposables.add(instantiationService.createInstance(UntitledTextEditorInput, service.create())); - const originalModel = await originalInput.resolve(); + const originalModel = disposables.add(await originalInput.resolve()); originalModel.textEditorModel?.setValue(content); const backup = await originalModel.backup(CancellationToken.None); const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: restoredInput.resource }; await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); - const restoredModel = await restoredInput.resolve(); + const restoredModel = disposables.add(await restoredInput.resolve()); assert.strictEqual(restoredModel.textEditorModel?.getValue(), content); assert.strictEqual(restoredModel.isDirty(), true); - - originalInput.dispose(); - originalModel.dispose(); - restoredInput.dispose(); - restoredModel.dispose(); } + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index 7135c0be441..23aff4b0fe5 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { equals } from 'vs/base/common/objects'; import { ThemeIcon } from 'vs/base/common/themables'; -import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { defaultUserDataProfileIcon, DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class UserDataProfileService extends Disposable implements IUserDataProfileService { @@ -22,8 +22,7 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi get currentProfile(): IUserDataProfile { return this._currentProfile; } constructor( - currentProfile: IUserDataProfile, - @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService + currentProfile: IUserDataProfile ) { super(); this._currentProfile = currentProfile; diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index e5587cb3cda..82ba6f40652 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService, IUserDataSyncResource, IResourcePreview, USER_DATA_SYNC_SCHEME, } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService, IUserDataSyncResource, IResourcePreview, USER_DATA_SYNC_SCHEME, USER_DATA_SYNC_LOG_ID, } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_CONFLICTS_VIEW_ID, CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW, CONTEXT_HAS_CONFLICTS, IUserDataSyncConflictsView } from 'vs/workbench/services/userDataSync/common/userDataSync'; @@ -38,6 +38,7 @@ import { isDiffEditorInput } from 'vs/workbench/common/editor'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; +import { IFileService } from 'vs/platform/files/common/files'; type AccountQuickPickItem = { label: string; authenticationProvider: IAuthenticationProvider; account?: UserDataSyncAccount; description?: string }; @@ -113,6 +114,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, + @IFileService private readonly fileService: IFileService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); @@ -448,12 +450,44 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } + async getAllLogResources(): Promise { + const logsFolders: URI[] = []; + const stat = await this.fileService.resolve(this.uriIdentityService.extUri.dirname(this.environmentService.logsHome)); + if (stat.children) { + logsFolders.push(...stat.children + .filter(stat => stat.isDirectory && /^\d{8}T\d{6}$/.test(stat.name)) + .sort() + .reverse() + .map(d => d.resource)); + } + const result: URI[] = []; + for (const logFolder of logsFolders) { + const folderStat = await this.fileService.resolve(logFolder); + const childStat = folderStat.children?.find(stat => this.uriIdentityService.extUri.basename(stat.resource).startsWith(`${USER_DATA_SYNC_LOG_ID}.`)); + if (childStat) { + result.push(childStat.resource); + } + } + return result; + } + async showSyncActivity(): Promise { this.activityViewsEnablementContext.set(true); await this.waitForActiveSyncViews(); await this.viewsService.openViewContainer(SYNC_VIEW_CONTAINER_ID); } + async downloadSyncActivity(location: URI): Promise { + await Promise.all([ + this.userDataSyncService.saveRemoteActivityData(this.uriIdentityService.extUri.joinPath(location, 'remoteActivity.json')), + (async () => { + const logResources = await this.getAllLogResources(); + await Promise.all(logResources.map(async logResource => this.fileService.copy(logResource, this.uriIdentityService.extUri.joinPath(location, 'logs', `${this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(logResource))}.log`)))); + })(), + this.fileService.copy(this.environmentService.userDataSyncHome, this.uriIdentityService.extUri.joinPath(location, 'localActivity')), + ]); + } + private async waitForActiveSyncViews(): Promise { const viewContainer = this.viewDescriptorService.getViewContainerById(SYNC_VIEW_CONTAINER_ID); if (viewContainer) { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index ace123f1533..0bc33984d51 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -43,6 +43,9 @@ export interface IUserDataSyncWorkbenchService { showConflicts(conflictToOpen?: IResourcePreview): Promise; accept(resource: IUserDataSyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise; + + getAllLogResources(): Promise; + downloadSyncActivity(location: URI): Promise; } export function getSyncAreaLabel(source: SyncResource): string { diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts index 59ca1f122e8..4825bb8d580 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncResourceProviderService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { UserDataSyncChannelClient } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; registerSharedProcessRemoteService(IUserDataSyncService, 'userDataSync', { channelClientCtor: UserDataSyncChannelClient }); +registerSharedProcessRemoteService(IUserDataSyncResourceProviderService, 'IUserDataSyncResourceProviderService'); diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 7798c65acd1..101672ef058 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -13,20 +13,18 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { URI } from 'vs/base/common/uri'; import { coalesce, move } from 'vs/base/common/arrays'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; -import { isEqual, joinPath } from 'vs/base/common/resources'; +import { isEqual } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/base/common/themables'; import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOutputService } from 'vs/workbench/services/output/common/output'; import { CounterSet } from 'vs/base/common/map'; -const VIEWS_LOG_ID = 'viewsLog'; +const VIEWS_LOG_ID = 'views'; const VIEWS_LOG_NAME = localize('views log', "Views"); -function getViewsLogFile(environmentService: IWorkbenchEnvironmentService): URI { return joinPath(environmentService.windowLogsPath, 'views.log'); } registerAction2(class extends Action2 { constructor() { @@ -40,10 +38,8 @@ registerAction2(class extends Action2 { async run(servicesAccessor: ServicesAccessor): Promise { const loggerService = servicesAccessor.get(ILoggerService); const outputService = servicesAccessor.get(IOutputService); - const environmentService = servicesAccessor.get(IWorkbenchEnvironmentService); - loggerService.setVisibility(getViewsLogFile(environmentService), true); + loggerService.setVisibility(VIEWS_LOG_ID, true); outputService.showChannel(VIEWS_LOG_ID); - } }); @@ -87,11 +83,10 @@ class ViewDescriptorsState extends Disposable { private readonly viewContainerName: string, @IStorageService private readonly storageService: IStorageService, @ILoggerService loggerService: ILoggerService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { super(); - this.logger = loggerService.createLogger(getViewsLogFile(workbenchEnvironmentService), { id: VIEWS_LOG_ID, name: VIEWS_LOG_NAME, hidden: true }); + this.logger = loggerService.createLogger(VIEWS_LOG_ID, { name: VIEWS_LOG_NAME, hidden: true }); this.globalViewsStateStorageId = getViewsStateStorageId(viewContainerStorageId); this.workspaceViewsStateStorageId = viewContainerStorageId; @@ -358,11 +353,10 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILoggerService loggerService: ILoggerService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { super(); - this.logger = loggerService.createLogger(getViewsLogFile(workbenchEnvironmentService), { id: VIEWS_LOG_ID, name: VIEWS_LOG_NAME, hidden: true }); + this.logger = loggerService.createLogger(VIEWS_LOG_ID, { name: VIEWS_LOG_NAME, hidden: true }); this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, viewContainer.storageId || `${viewContainer.id}.state`, typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.original)); diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts index 57723675e03..9a39dda2611 100644 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views'; -import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -19,6 +19,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { Event } from 'vs/base/common/event'; import { getViewsStateStorageId } from 'vs/workbench/services/views/common/viewContainerModel'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const ViewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -43,22 +44,20 @@ class ViewDescriptorSequence { suite('ViewContainerModel', () => { let container: ViewContainer; - let disposableStore: DisposableStore; + const disposableStore = ensureNoDisposablesAreLeakedInTestSuite(); let contextKeyService: IContextKeyService; let viewDescriptorService: IViewDescriptorService; let storageService: IStorageService; setup(() => { - disposableStore = new DisposableStore(); const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposableStore); - contextKeyService = instantiationService.createInstance(ContextKeyService); + contextKeyService = disposableStore.add(instantiationService.createInstance(ContextKeyService)); instantiationService.stub(IContextKeyService, contextKeyService); storageService = instantiationService.get(IStorageService); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); + viewDescriptorService = disposableStore.add(instantiationService.createInstance(ViewDescriptorService)); }); teardown(() => { - disposableStore.dispose(); ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); ViewContainerRegistry.deregisterViewContainer(container); }); @@ -380,7 +379,7 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidRemoveVisibleViewDescriptors(targetEvent); + disposableStore.add(testObject.onDidRemoveVisibleViewDescriptors(targetEvent)); key.set(false); await new Promise(c => setTimeout(c, 30)); assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); @@ -406,7 +405,7 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(targetEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(targetEvent)); testObject.setVisible('view1', true); assert.ok(!targetEvent.called, 'add event should not be called since it is already visible'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); @@ -433,7 +432,7 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(targetEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(targetEvent)); testObject.setVisible('view1', false); assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); @@ -464,7 +463,7 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(targetEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(targetEvent)); testObject.setVisible('view1', true); assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); @@ -543,8 +542,8 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(targetEvent); - Event.once(testObject.onDidChangeActiveViewDescriptors)(() => testObject.setVisible('view1', true)); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(targetEvent)); + disposableStore.add(Event.once(testObject.onDidChangeActiveViewDescriptors)(() => testObject.setVisible('view1', true))); key.set(true); await new Promise(c => setTimeout(c, 30)); assert.strictEqual(targetEvent.callCount, 1); @@ -573,8 +572,8 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); const targetEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(targetEvent); - Event.once(testObject.onDidChangeActiveViewDescriptors)(() => testObject.setVisible('view1', false)); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(targetEvent)); + disposableStore.add(Event.once(testObject.onDidChangeActiveViewDescriptors)(() => testObject.setVisible('view1', false))); key.set(true); await new Promise(c => setTimeout(c, 30)); assert.strictEqual(targetEvent.callCount, 0); @@ -631,10 +630,10 @@ suite('ViewContainerModel', () => { ViewsRegistry.registerViews([viewDescriptor1, viewDescriptor2, viewDescriptor3], container); const remomveEvent = sinon.spy(); - testObject.onDidRemoveVisibleViewDescriptors(remomveEvent); + disposableStore.add(testObject.onDidRemoveVisibleViewDescriptors(remomveEvent)); const addEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(addEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(addEvent)); storageService.store(getViewsStateStorageId('test.state'), JSON.stringify([{ id: viewDescriptor1.id, @@ -691,10 +690,10 @@ suite('ViewContainerModel', () => { testObject.setVisible(viewDescriptor3.id, false); const removeEvent = sinon.spy(); - testObject.onDidRemoveVisibleViewDescriptors(removeEvent); + disposableStore.add(testObject.onDidRemoveVisibleViewDescriptors(removeEvent)); const addEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(addEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(addEvent)); storageService.store(getViewsStateStorageId('test.state'), JSON.stringify([{ id: viewDescriptor1.id, @@ -764,10 +763,10 @@ suite('ViewContainerModel', () => { testObject.setVisible(viewDescriptor1.id, false); const removeEvent = sinon.spy(); - testObject.onDidRemoveVisibleViewDescriptors(removeEvent); + disposableStore.add(testObject.onDidRemoveVisibleViewDescriptors(removeEvent)); const addEvent = sinon.spy(); - testObject.onDidAddVisibleViewDescriptors(addEvent); + disposableStore.add(testObject.onDidAddVisibleViewDescriptors(addEvent)); storageService.store(getViewsStateStorageId('test.state'), JSON.stringify([{ id: viewDescriptor1.id, diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index 4a1c167c07f..481b0c25bd5 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -13,10 +13,10 @@ import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewD import { assertIsDefined } from 'vs/base/common/types'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { generateUuid } from 'vs/base/common/uuid'; import { compare } from 'vs/base/common/strings'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); const ViewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); @@ -26,12 +26,12 @@ const panelContainer = ViewContainersRegistry.registerViewContainer({ id: `${vie suite('ViewDescriptorService', () => { - const disposables = new DisposableStore(); + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; setup(() => { disposables.add(instantiationService = workbenchInstantiationService(undefined, disposables)); - instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); + instantiationService.stub(IContextKeyService, disposables.add(instantiationService.createInstance(ContextKeyService))); }); teardown(() => { @@ -40,7 +40,6 @@ suite('ViewDescriptorService', () => { ViewsRegistry.deregisterViews(ViewsRegistry.getViews(viewContainer), viewContainer); } } - disposables.clear(); }); function aViewDescriptorService(): ViewDescriptorService { @@ -222,7 +221,6 @@ suite('ViewDescriptorService', () => { let expectedSequence = ''; let actualSequence = ''; - const disposables = []; const containerMoveString = (view: IViewDescriptor, from: ViewContainer, to: ViewContainer) => { return `Moved ${view.id} from ${from.id} to ${to.id}\n`; @@ -231,13 +229,13 @@ suite('ViewDescriptorService', () => { const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; }; - disposables.push(testObject.onDidChangeContainer(({ views, from, to }) => { + disposables.add(testObject.onDidChangeContainer(({ views, from, to }) => { views.forEach(view => { actualSequence += containerMoveString(view, from, to); }); })); - disposables.push(testObject.onDidChangeLocation(({ views, from, to }) => { + disposables.add(testObject.onDidChangeLocation(({ views, from, to }) => { views.forEach(view => { actualSequence += locationMoveString(view, from, to); }); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 00ab0443125..6332060450e 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -199,7 +199,7 @@ export class FileWorkingCopyManager // Lifecycle if (isWeb) { - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdownWeb(), 'veto.fileWorkingCopyManager')); + this._register(this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdownWeb(), 'veto.fileWorkingCopyManager'))); } else { - this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdownDesktop(), { id: 'join.fileWorkingCopyManager', label: localize('join.fileWorkingCopyManager', "Saving working copies") })); + this._register(this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdownDesktop(), { id: 'join.fileWorkingCopyManager', label: localize('join.fileWorkingCopyManager', "Saving working copies") }))); } } diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts index 11bddb67625..86177563f33 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopySaveParticipant.ts @@ -61,6 +61,9 @@ export class StoredFileWorkingCopySaveParticipant extends Disposable { // undoStop after participation workingCopy.model?.pushStackElement(); + + // Cleanup + cts.dispose(); }, () => { // user cancel cts.dispose(true); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index a5f58b91345..6b3fb90c562 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -7,7 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore, DisposableMap } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { IWorkingCopy, IWorkingCopyIdentifier, IWorkingCopySaveEvent as IBaseWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -167,6 +167,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic private _workingCopies = new Set(); private readonly mapResourceToWorkingCopies = new ResourceMap>(); + private readonly mapWorkingCopyToListeners = this._register(new DisposableMap()); registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable { let workingCopiesForResource = this.mapResourceToWorkingCopies.get(workingCopy.resource); @@ -189,6 +190,7 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic disposables.add(workingCopy.onDidChangeContent(() => this._onDidChangeContent.fire(workingCopy))); disposables.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); disposables.add(workingCopy.onDidSave(e => this._onDidSave.fire({ workingCopy, ...e }))); + this.mapWorkingCopyToListeners.set(workingCopy, disposables); // Send some initial events this._onDidRegister.fire(workingCopy); @@ -197,8 +199,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic } return toDisposable(() => { + + // Unregister working copy this.unregisterWorkingCopy(workingCopy); - dispose(disposables); // Signal as event this._onDidUnregister.fire(workingCopy); @@ -221,6 +224,9 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic if (workingCopy.isDirty()) { this._onDidChangeDirty.fire(workingCopy); } + + // Remove all listeners associated to working copy + this.mapWorkingCopyToListeners.deleteAndDispose(workingCopy); } has(identifier: IWorkingCopyIdentifier): boolean; diff --git a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts index c048929e21c..16185733f61 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/fileWorkingCopyManager.test.ts @@ -15,6 +15,7 @@ import { IFileWorkingCopyManager, FileWorkingCopyManager } from 'vs/workbench/se import { TestUntitledFileWorkingCopyModel, TestUntitledFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test'; import { UntitledFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('FileWorkingCopyManager', () => { @@ -49,9 +50,9 @@ suite('FileWorkingCopyManager', () => { test('onDidCreate, get, workingCopies', async () => { let createCounter = 0; - manager.onDidCreate(e => { + disposables.add(manager.onDidCreate(e => { createCounter++; - }); + })); const fileUri = URI.file('/test.html'); @@ -66,29 +67,23 @@ suite('FileWorkingCopyManager', () => { assert.strictEqual(manager.get(fileWorkingCopy.resource), fileWorkingCopy); assert.strictEqual(manager.get(untitledFileWorkingCopy.resource), untitledFileWorkingCopy); - const sameFileWorkingCopy = await manager.resolve(fileUri); - const sameUntitledFileWorkingCopy = await manager.resolve({ untitledResource: untitledFileWorkingCopy.resource }); + const sameFileWorkingCopy = disposables.add(await manager.resolve(fileUri)); + const sameUntitledFileWorkingCopy = disposables.add(await manager.resolve({ untitledResource: untitledFileWorkingCopy.resource })); assert.strictEqual(sameFileWorkingCopy, fileWorkingCopy); assert.strictEqual(sameUntitledFileWorkingCopy, untitledFileWorkingCopy); assert.strictEqual(manager.workingCopies.length, 2); assert.strictEqual(createCounter, 2); - - fileWorkingCopy.dispose(); - untitledFileWorkingCopy.dispose(); }); test('resolve', async () => { - const fileWorkingCopy = await manager.resolve(URI.file('/test.html')); + const fileWorkingCopy = disposables.add(await manager.resolve(URI.file('/test.html'))); assert.ok(fileWorkingCopy instanceof StoredFileWorkingCopy); assert.strictEqual(await manager.stored.resolve(fileWorkingCopy.resource), fileWorkingCopy); - const untitledFileWorkingCopy = await manager.resolve(); + const untitledFileWorkingCopy = disposables.add(await manager.resolve()); assert.ok(untitledFileWorkingCopy instanceof UntitledFileWorkingCopy); assert.strictEqual(await manager.untitled.resolve({ untitledResource: untitledFileWorkingCopy.resource }), untitledFileWorkingCopy); assert.strictEqual(await manager.resolve(untitledFileWorkingCopy.resource), untitledFileWorkingCopy); - - fileWorkingCopy.dispose(); - untitledFileWorkingCopy.dispose(); }); test('destroy', async () => { @@ -184,14 +179,14 @@ suite('FileWorkingCopyManager', () => { async function testSaveAsFile(source: URI, target: URI, resolveSource: boolean, resolveTarget: boolean) { let sourceWorkingCopy: IStoredFileWorkingCopy | undefined = undefined; if (resolveSource) { - sourceWorkingCopy = await manager.resolve(source); + sourceWorkingCopy = disposables.add(await manager.resolve(source)); sourceWorkingCopy.model?.updateContents('hello world'); assert.ok(sourceWorkingCopy.isDirty()); } let targetWorkingCopy: IStoredFileWorkingCopy | undefined = undefined; if (resolveTarget) { - targetWorkingCopy = await manager.resolve(target); + targetWorkingCopy = disposables.add(await manager.resolve(target)); targetWorkingCopy.model?.updateContents('hello world'); assert.ok(targetWorkingCopy.isDirty()); } @@ -222,10 +217,12 @@ suite('FileWorkingCopyManager', () => { if (resolveTarget) { assert.strictEqual(targetWorkingCopy?.isDirty(), false); } + + result?.dispose(); } test('saveAs - untitled (without associated resource)', async () => { - const workingCopy = await manager.resolve(); + const workingCopy = disposables.add(await manager.resolve()); workingCopy.model?.updateContents('Simple Save As'); const target = URI.file('simple/file.txt'); @@ -238,11 +235,11 @@ suite('FileWorkingCopyManager', () => { assert.strictEqual(manager.untitled.get(workingCopy.resource), undefined); - workingCopy.dispose(); + result?.dispose(); }); test('saveAs - untitled (with associated resource)', async () => { - const workingCopy = await manager.resolve({ associatedResource: { path: '/some/associated.txt' } }); + const workingCopy = disposables.add(await manager.resolve({ associatedResource: { path: '/some/associated.txt' } })); workingCopy.model?.updateContents('Simple Save As with associated resource'); const target = URI.from({ scheme: Schemas.file, path: '/some/associated.txt' }); @@ -256,11 +253,11 @@ suite('FileWorkingCopyManager', () => { assert.strictEqual(manager.untitled.get(workingCopy.resource), undefined); - workingCopy.dispose(); + result?.dispose(); }); test('saveAs - untitled (target exists and is resolved)', async () => { - const workingCopy = await manager.resolve(); + const workingCopy = disposables.add(await manager.resolve()); workingCopy.model?.updateContents('Simple Save As'); const target = URI.file('simple/file.txt'); @@ -274,6 +271,8 @@ suite('FileWorkingCopyManager', () => { assert.strictEqual(manager.untitled.get(workingCopy.resource), undefined); - workingCopy.dispose(); + result?.dispose(); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts index d4d9097c020..fee71d4e7d0 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts @@ -55,7 +55,7 @@ suite('ResourceWorkingCopy', function () { }); test('orphaned tracking', async () => { - runWithFakedTimers({}, async () => { + return runWithFakedTimers({}, async () => { assert.strictEqual(workingCopy.isOrphaned(), false); let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index f87ad8050e8..ba5268aff39 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -216,6 +216,12 @@ suite('StoredFileWorkingCopy', function () { }); teardown(() => { + workingCopy.dispose(); + + for (const workingCopy of accessor.workingCopyService.workingCopies) { + (workingCopy as StoredFileWorkingCopy).dispose(); + } + disposables.clear(); }); @@ -228,7 +234,7 @@ suite('StoredFileWorkingCopy', function () { }); test('orphaned tracking', async () => { - runWithFakedTimers({}, async () => { + return runWithFakedTimers({}, async () => { assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false); let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned); @@ -256,19 +262,19 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(workingCopy.isResolved(), true); let changeDirtyCounter = 0; - workingCopy.onDidChangeDirty(() => { + disposables.add(workingCopy.onDidChangeDirty(() => { changeDirtyCounter++; - }); + })); let contentChangeCounter = 0; - workingCopy.onDidChangeContent(() => { + disposables.add(workingCopy.onDidChangeContent(() => { contentChangeCounter++; - }); + })); let savedCounter = 0; - workingCopy.onDidSave(() => { + disposables.add(workingCopy.onDidSave(() => { savedCounter++; - }); + })); // Dirty from: Model content change workingCopy.model?.updateContents('hello dirty'); @@ -338,9 +344,9 @@ suite('StoredFileWorkingCopy', function () { test('resolve (without backup)', async () => { let onDidResolveCounter = 0; - workingCopy.onDidResolve(() => { + disposables.add(workingCopy.onDidResolve(() => { onDidResolveCounter++; - }); + })); // resolve from file await workingCopy.resolve(); @@ -405,7 +411,7 @@ suite('StoredFileWorkingCopy', function () { }); test('resolve (with backup, preserves metadata and orphaned state)', async () => { - runWithFakedTimers({}, async () => { + return runWithFakedTimers({}, async () => { await workingCopy.resolve({ contents: bufferToStream(VSBuffer.fromString('hello backup')) }); const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned); @@ -434,7 +440,7 @@ suite('StoredFileWorkingCopy', function () { }); test('resolve (updates orphaned state accordingly)', async () => { - runWithFakedTimers({}, async () => { + return runWithFakedTimers({}, async () => { await workingCopy.resolve(); const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned); @@ -476,7 +482,7 @@ suite('StoredFileWorkingCopy', function () { test('resolve (FILE_NOT_MODIFIED_SINCE still updates readonly state)', async () => { let readonlyChangeCounter = 0; - workingCopy.onDidChangeReadonly(() => readonlyChangeCounter++); + disposables.add(workingCopy.onDidChangeReadonly(() => readonlyChangeCounter++)); await workingCopy.resolve(); @@ -541,15 +547,15 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - simple', async () => { let savedCounter = 0; let lastSaveEvent: IStoredFileWorkingCopySaveEvent | undefined = undefined; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; lastSaveEvent = e; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // unresolved await workingCopy.save(); @@ -573,15 +579,15 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - save reason', async () => { let savedCounter = 0; let lastSaveEvent: IStoredFileWorkingCopySaveEvent | undefined = undefined; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; lastSaveEvent = e; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // save reason await workingCopy.resolve(); @@ -599,14 +605,14 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - multiple', async () => { let savedCounter = 0; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // multiple saves in parallel are fine and result // in a single save when content does not change @@ -625,14 +631,14 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - multiple, cancellation', async () => { let savedCounter = 0; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // multiple saves in parallel are fine and result // in just one save operation (the second one @@ -651,14 +657,14 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - not forced but not dirty', async () => { let savedCounter = 0; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // no save when not forced and not dirty await workingCopy.resolve(); @@ -670,14 +676,14 @@ suite('StoredFileWorkingCopy', function () { test('save (no errors) - forced but not dirty', async () => { let savedCounter = 0; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); // save when forced even when not dirty await workingCopy.resolve(); @@ -688,16 +694,16 @@ suite('StoredFileWorkingCopy', function () { }); test('save (no errors) - save clears orphaned', async () => { - runWithFakedTimers({}, async () => { + return runWithFakedTimers({}, async () => { let savedCounter = 0; - workingCopy.onDidSave(e => { + disposables.add(workingCopy.onDidSave(e => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); await workingCopy.resolve(); @@ -720,14 +726,14 @@ suite('StoredFileWorkingCopy', function () { test('save (errors)', async () => { let savedCounter = 0; - workingCopy.onDidSave(reason => { + disposables.add(workingCopy.onDidSave(reason => { savedCounter++; - }); + })); let saveErrorCounter = 0; - workingCopy.onDidSaveError(() => { + disposables.add(workingCopy.onDidSaveError(() => { saveErrorCounter++; - }); + })); await workingCopy.resolve(); @@ -898,9 +904,9 @@ suite('StoredFileWorkingCopy', function () { workingCopy.model?.updateContents('hello revert'); let revertedCounter = 0; - workingCopy.onDidRevert(() => { + disposables.add(workingCopy.onDidRevert(() => { revertedCounter++; - }); + })); // revert: soft await workingCopy.revert({ soft: true }); @@ -994,14 +1000,14 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(workingCopy.isDisposed(), false); let disposedEvent = false; - workingCopy.onWillDispose(() => { + disposables.add(workingCopy.onWillDispose(() => { disposedEvent = true; - }); + })); let disposedModelEvent = false; - workingCopy.model.onWillDispose(() => { + disposables.add(workingCopy.model.onWillDispose(() => { disposedModelEvent = true; - }); + })); workingCopy.dispose(); @@ -1020,13 +1026,15 @@ suite('StoredFileWorkingCopy', function () { accessor.fileService.readonly = false; let readonlyEvent = false; - workingCopy.onDidChangeReadonly(() => { + disposables.add(workingCopy.onDidChangeReadonly(() => { readonlyEvent = true; - }); + })); await workingCopy.resolve(); assert.strictEqual(workingCopy.isReadonly(), false); assert.strictEqual(readonlyEvent, true); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts index 4a63529c127..54366a2df21 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts @@ -17,6 +17,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('StoredFileWorkingCopyManager', () => { @@ -41,6 +42,10 @@ suite('StoredFileWorkingCopyManager', () => { }); teardown(() => { + for (const workingCopy of manager.workingCopies) { + workingCopy.dispose(); + } + disposables.clear(); }); @@ -386,6 +391,10 @@ suite('StoredFileWorkingCopyManager', () => { // dispose does not remove from working copy service, only `destroy` should assert.strictEqual(accessor.workingCopyService.workingCopies.length, 3); + + disposables.add(await firstPromise); + disposables.add(await secondPromise); + disposables.add(await thirdPromise); }); test('destroy', async () => { @@ -416,9 +425,9 @@ suite('StoredFileWorkingCopyManager', () => { const workingCopy = await manager.resolve(resource); let saved = false; - workingCopy.onDidSave(() => { + disposables.add(workingCopy.onDidSave(() => { saved = true; - }); + })); workingCopy.model?.updateContents('hello create'); assert.strictEqual(workingCopy.isDirty(), true); @@ -441,9 +450,9 @@ suite('StoredFileWorkingCopyManager', () => { workingCopy.model?.setThrowOnSnapshot(); let unexpectedSave = false; - workingCopy.onDidSave(() => { + disposables.add(workingCopy.onDidSave(() => { unexpectedSave = true; - }); + })); workingCopy.model?.updateContents('hello create'); assert.strictEqual(workingCopy.isDirty(), true); @@ -468,12 +477,12 @@ suite('StoredFileWorkingCopyManager', () => { let didResolve = false; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model?.resource.toString() === resource.toString()) { didResolve = true; resolve(); } - }); + })); }); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }], false)); @@ -491,7 +500,7 @@ suite('StoredFileWorkingCopyManager', () => { let didResolve = false; let resolvedCounter = 0; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model?.resource.toString() === resource.toString()) { resolvedCounter++; if (resolvedCounter === 2) { @@ -499,7 +508,7 @@ suite('StoredFileWorkingCopyManager', () => { resolve(); } } - }); + })); }); accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }], false)); @@ -512,19 +521,19 @@ suite('StoredFileWorkingCopyManager', () => { test('file system provider change triggers working copy resolve', async () => { const resource = URI.file('/path/index.txt'); - await manager.resolve(resource); + disposables.add(await manager.resolve(resource)); let didResolve = false; const onDidResolve = new Promise(resolve => { - manager.onDidResolve(({ model }) => { + disposables.add(manager.onDidResolve(({ model }) => { if (model?.resource.toString() === resource.toString()) { didResolve = true; resolve(); } - }); + })); }); - accessor.fileService.fireFileSystemProviderCapabilitiesChangeEvent({ provider: new InMemoryFileSystemProvider(), scheme: resource.scheme }); + accessor.fileService.fireFileSystemProviderCapabilitiesChangeEvent({ provider: disposables.add(new InMemoryFileSystemProvider()), scheme: resource.scheme }); await onDidResolve; @@ -647,4 +656,6 @@ suite('StoredFileWorkingCopyManager', () => { assert.strictEqual(saved1, true); assert.strictEqual(saved2, true); }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts index 32799349d46..cfac100bd5b 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts @@ -8,6 +8,7 @@ import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; import { NO_TYPE_ID, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -27,8 +28,8 @@ suite('UntitledFileWorkingCopyManager', () => { instantiationService = workbenchInstantiationService(undefined, disposables); accessor = instantiationService.createInstance(TestServiceAccessor); - accessor.fileService.registerProvider(Schemas.file, new TestInMemoryFileSystemProvider()); - accessor.fileService.registerProvider(Schemas.vscodeRemote, new TestInMemoryFileSystemProvider()); + disposables.add(accessor.fileService.registerProvider(Schemas.file, disposables.add(new TestInMemoryFileSystemProvider()))); + disposables.add(accessor.fileService.registerProvider(Schemas.vscodeRemote, disposables.add(new TestInMemoryFileSystemProvider()))); manager = disposables.add(new FileWorkingCopyManager( 'testUntitledFileWorkingCopyType', @@ -43,24 +44,28 @@ suite('UntitledFileWorkingCopyManager', () => { }); teardown(() => { + for (const workingCopy of [...manager.untitled.workingCopies, ...manager.stored.workingCopies]) { + workingCopy.dispose(); + } + disposables.clear(); }); test('basics', async () => { let createCounter = 0; - manager.untitled.onDidCreate(e => { + disposables.add(manager.untitled.onDidCreate(e => { createCounter++; - }); + })); let disposeCounter = 0; - manager.untitled.onWillDispose(e => { + disposables.add(manager.untitled.onWillDispose(e => { disposeCounter++; - }); + })); let dirtyCounter = 0; - manager.untitled.onDidChangeDirty(e => { + disposables.add(manager.untitled.onDidChangeDirty(e => { dirtyCounter++; - }); + })); assert.strictEqual(accessor.workingCopyService.workingCopies.length, 0); assert.strictEqual(manager.untitled.workingCopies.length, 0); @@ -122,9 +127,9 @@ suite('UntitledFileWorkingCopyManager', () => { test('dirty - scratchpads are never dirty', async () => { let dirtyCounter = 0; - manager.untitled.onDidChangeDirty(e => { + disposables.add(manager.untitled.onDidChangeDirty(e => { dirtyCounter++; - }); + })); const workingCopy1 = await manager.resolve({ untitledResource: URI.from({ scheme: Schemas.untitled, path: `/myscratchpad` }), @@ -149,9 +154,9 @@ suite('UntitledFileWorkingCopyManager', () => { test('resolve - with initial value', async () => { let dirtyCounter = 0; - manager.untitled.onDidChangeDirty(e => { + disposables.add(manager.untitled.onDidChangeDirty(e => { dirtyCounter++; - }); + })); const workingCopy1 = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')) } }); @@ -174,9 +179,9 @@ suite('UntitledFileWorkingCopyManager', () => { test('resolve - with initial value but markDirty: false', async () => { let dirtyCounter = 0; - manager.untitled.onDidChangeDirty(e => { + disposables.add(manager.untitled.onDidChangeDirty(e => { dirtyCounter++; - }); + })); const workingCopy = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')), markModified: false } }); @@ -192,15 +197,15 @@ suite('UntitledFileWorkingCopyManager', () => { const untitled1 = await manager.untitled.resolve(); untitled1.dispose(); - const untitled1Again = await manager.untitled.resolve(); + const untitled1Again = disposables.add(await manager.untitled.resolve()); assert.strictEqual(untitled1.resource.toString(), untitled1Again.resource.toString()); }); test('resolve - existing', async () => { let createCounter = 0; - manager.untitled.onDidCreate(e => { + disposables.add(manager.untitled.onDidCreate(e => { createCounter++; - }); + })); const workingCopy1 = await manager.untitled.resolve(); assert.strictEqual(createCounter, 1); @@ -305,7 +310,7 @@ suite('UntitledFileWorkingCopyManager', () => { test('manager with different types produce different URIs', async () => { try { - manager = new FileWorkingCopyManager( + manager = disposables.add(new FileWorkingCopyManager( 'someOtherUntitledTypeId', new TestStoredFileWorkingCopyModelFactory(), new TestUntitledFileWorkingCopyModelFactory(), @@ -314,10 +319,10 @@ suite('UntitledFileWorkingCopyManager', () => { accessor.filesConfigurationService, accessor.workingCopyService, accessor.notificationService, accessor.workingCopyEditorService, accessor.editorService, accessor.elevatedFileService, accessor.pathService, accessor.environmentService, accessor.dialogService, accessor.decorationsService - ); + )); - const untitled1OriginalType = await manager.untitled.resolve(); - const untitled1OtherType = await manager.untitled.resolve(); + const untitled1OriginalType = disposables.add(await manager.untitled.resolve()); + const untitled1OtherType = disposables.add(await manager.untitled.resolve()); assert.notStrictEqual(untitled1OriginalType.resource.toString(), untitled1OtherType.resource.toString()); } finally { @@ -327,7 +332,7 @@ suite('UntitledFileWorkingCopyManager', () => { test('manager without typeId produces backwards compatible URIs', async () => { try { - manager = new FileWorkingCopyManager( + manager = disposables.add(new FileWorkingCopyManager( NO_TYPE_ID, new TestStoredFileWorkingCopyModelFactory(), new TestUntitledFileWorkingCopyModelFactory(), @@ -336,9 +341,9 @@ suite('UntitledFileWorkingCopyManager', () => { accessor.filesConfigurationService, accessor.workingCopyService, accessor.notificationService, accessor.workingCopyEditorService, accessor.editorService, accessor.elevatedFileService, accessor.pathService, accessor.environmentService, accessor.dialogService, accessor.decorationsService - ); + )); - const result = await manager.untitled.resolve(); + const result = disposables.add(await manager.untitled.resolve()); assert.strictEqual(result.resource.scheme, Schemas.untitled); assert.ok(result.resource.path.length > 0); assert.strictEqual(result.resource.query, ''); @@ -348,4 +353,6 @@ suite('UntitledFileWorkingCopyManager', () => { manager.destroy(); } }); + + ensureNoDisposablesAreLeakedInTestSuite(); }); diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts index 92da56329ee..fe5d207307e 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts @@ -32,6 +32,8 @@ import { INativeWindowConfiguration } from 'vs/platform/window/common/window'; import product from 'vs/platform/product/common/product'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; const homeDir = URI.file('home').with({ scheme: Schemas.inMemory }); const tmpDir = URI.file('tmp').with({ scheme: Schemas.inMemory }); @@ -96,7 +98,9 @@ export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupSer const fsp = new InMemoryFileSystemProvider(); fileService.registerProvider(Schemas.inMemory, fsp); - fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, fsp, Schemas.vscodeUserData, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, fsp, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService)); this._fileService = fileService; diff --git a/src/vs/workbench/test/browser/webview.test.ts b/src/vs/workbench/test/browser/webview.test.ts index 2eac5352c0a..484be00a657 100644 --- a/src/vs/workbench/test/browser/webview.test.ts +++ b/src/vs/workbench/test/browser/webview.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parentOriginHash } from 'vs/workbench/browser/iframe'; +import { parentOriginHash } from 'vs/base/browser/iframe'; suite('parentOriginHash', () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 3fa67419252..ab2bc3c34ea 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -291,7 +291,7 @@ export function workbenchInstantiationService( instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService(contextKeyService, configService, workspaceContextService, environmentService, uriIdentityService, fileService))); instantiationService.stub(IUriIdentityService, disposables.add(uriIdentityService)); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, new NullLogService()))); - instantiationService.stub(IUserDataProfileService, disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService))); + instantiationService.stub(IUserDataProfileService, disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile))); instantiationService.stub(IWorkingCopyBackupService, overrides?.workingCopyBackupService ? overrides?.workingCopyBackupService(instantiationService) : disposables.add(new TestWorkingCopyBackupService())); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); diff --git a/src/vs/workbench/test/common/utils.ts b/src/vs/workbench/test/common/utils.ts index f4a175f315a..b28eb1d1c8b 100644 --- a/src/vs/workbench/test/common/utils.ts +++ b/src/vs/workbench/test/common/utils.ts @@ -18,5 +18,5 @@ export function assertCleanState(): void { // If this test fails, it is a clear indication that // your test or suite is leaking services (e.g. via leaking text models) // assert.strictEqual(LanguageService.instanceCount, 0, 'No leaking ILanguageService'); - assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'No leaking LanguagesRegistry'); + assert.strictEqual(LanguagesRegistry.instanceCount, 0, 'Error: Test run should not leak in LanguagesRegistry.'); } diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index b88818f68fa..b5ad4d0e96f 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -46,6 +46,8 @@ import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataPr import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { NativeWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export class TestSharedProcessService implements ISharedProcessService { @@ -230,7 +232,9 @@ export class TestNativeWorkingCopyBackupService extends NativeWorkingCopyBackupS const inMemoryFileSystemProvider = this._register(new InMemoryFileSystemProvider()); this._register(fileService.registerProvider(Schemas.inMemory, inMemoryFileSystemProvider)); - this._register(fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, inMemoryFileSystemProvider, Schemas.vscodeUserData, logService)))); + const uriIdentityService = this._register(new UriIdentityService(fileService)); + const userDataProfilesService = this._register(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); + this._register(fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, inMemoryFileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, logService)))); this.backupResourceJoiners = []; this.discardBackupJoiners = []; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 649a54cdb8c..d6109358040 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -75,9 +75,9 @@ import { IWorkbenchExtensionManagementService } from 'vs/workbench/services/exte import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { LogLevel } from 'vs/platform/log/common/log'; import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncLocalStoreService, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; +import { UserDataSyncLocalStoreService } from 'vs/platform/userDataSync/common/userDataSyncLocalStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; @@ -96,7 +96,7 @@ registerSingleton(IAccessibilityService, AccessibilityService, InstantiationType registerSingleton(IContextMenuService, ContextMenuService, InstantiationType.Delayed); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService, InstantiationType.Delayed); registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); -registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncLocalStoreService, UserDataSyncLocalStoreService, InstantiationType.Delayed); registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); registerSingleton(IUserDataSyncService, UserDataSyncService, InstantiationType.Delayed); registerSingleton(IUserDataSyncResourceProviderService, UserDataSyncResourceProviderService, InstantiationType.Delayed); diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 17ee7553c42..b5c09fe1617 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -10000,6 +10000,11 @@ declare module 'vscode' { */ export const onDidChangeTelemetryEnabled: Event; + /** + * An {@link Event} which fires when the default shell changes. + */ + export const onDidChangeShell: Event; + /** * Creates a new {@link TelemetryLogger telemetry logger}. * @@ -17990,19 +17995,4 @@ declare module 'vscode' { * enables reusing existing code without migrating to a specific promise implementation. Still, * we recommend the use of native promises which are available in this editor. */ -interface Thenable { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; -} +interface Thenable extends PromiseLike { } diff --git a/src/vscode-dts/vscode.proposed.extensionsAny.d.ts b/src/vscode-dts/vscode.proposed.extensionsAny.d.ts index 378e324fa3f..c53cd6d89f8 100644 --- a/src/vscode-dts/vscode.proposed.extensionsAny.d.ts +++ b/src/vscode-dts/vscode.proposed.extensionsAny.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - // https://github.com/microsoft/vscode/issues/145307 + // https://github.com/microsoft/vscode/issues/145307 @alexdima export interface Extension { diff --git a/src/vscode-dts/vscode.proposed.indentSize.d.ts b/src/vscode-dts/vscode.proposed.indentSize.d.ts index 0ce01fa218a..2a490dd2742 100644 --- a/src/vscode-dts/vscode.proposed.indentSize.d.ts +++ b/src/vscode-dts/vscode.proposed.indentSize.d.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/193077 @alexdima + /** * Represents a {@link TextEditor text editor}'s {@link TextEditor.options options}. */ diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index 88dc3ef60c7..9e3ead9be40 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima + // https://github.com/microsoft/vscode/issues/124024 @hediet export namespace languages { /** diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 39e1e22e191..174991827c7 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -27,9 +27,7 @@ declare module 'vscode' { // todo@API make classes export interface InteractiveEditorRequest { - session: InteractiveEditorSession; prompt: string; - selection: Selection; wholeRange: Range; attempt: number; @@ -60,22 +58,19 @@ declare module 'vscode' { export interface TextDocumentContext { document: TextDocument; selection: Selection; - action?: string; + } + + export interface InteractiveEditorSessionProviderMetadata { + label: string; } export interface InteractiveEditorSessionProvider { - label: string; // Create a session. The lifetime of this session is the duration of the editing session with the input mode widget. prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult; - provideInteractiveEditorResponse(request: InteractiveEditorRequest, token: CancellationToken): ProviderResult; - provideInteractiveEditorResponse2?(request: InteractiveEditorRequest, progress: Progress<{ message: string; edits: TextEdit[] }>, token: CancellationToken): ProviderResult; + provideInteractiveEditorResponse(session: S, request: InteractiveEditorRequest, progress: Progress<{ message: string; edits: TextEdit[] }>, token: CancellationToken): ProviderResult; - // eslint-disable-next-line local/vscode-dts-provider-naming - releaseInteractiveEditorSession?(session: S): any; - - // todo@API use enum instead of boolean // eslint-disable-next-line local/vscode-dts-provider-naming handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void; } @@ -210,7 +205,7 @@ declare module 'vscode' { export function sendInteractiveRequestToProvider(providerId: string, message: InteractiveSessionDynamicRequest): void; - export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider): Disposable; + export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider, metadata?: InteractiveEditorSessionProviderMetadata): Disposable; export function transferChatSession(session: InteractiveSession, toWorkspace: Uri): void; } diff --git a/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts b/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts index 641e2f5f0d8..3cea4fdf090 100644 --- a/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts +++ b/src/vscode-dts/vscode.proposed.languageConfigurationAutoClosingPairs.d.ts @@ -5,7 +5,7 @@ declare module 'vscode' { - // https://github.com/microsoft/vscode/issues/173738 + // https://github.com/microsoft/vscode/issues/173738 @alexdima export interface LanguageConfiguration { autoClosingPairs?: { diff --git a/test/automation/src/electron.ts b/test/automation/src/electron.ts index 4f1cb9b27e5..da2087a8faa 100644 --- a/test/automation/src/electron.ts +++ b/test/automation/src/electron.ts @@ -29,7 +29,7 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom '--disable-telemetry', '--no-cached-data', '--disable-updates', - '--disable-keytar', + '--use-inmemory-secretstorage', `--crash-reporter-directory=${crashesPath}`, '--disable-workspace-trust', `--extensions-dir=${extensionsPath}`, diff --git a/test/unit/electron/renderer.html b/test/unit/electron/renderer.html index 617a3bac138..5fcbc9661aa 100644 --- a/test/unit/electron/renderer.html +++ b/test/unit/electron/renderer.html @@ -23,16 +23,6 @@ window.alert = function () { throw new Error('window.alert() is not supported in tests!'); } window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); } - // Ignore uncaught cancelled promise errors - window.addEventListener('unhandledrejection', e => { - const name = e && e.reason && e.reason.name; - - if (name === 'Canceled') { - e.preventDefault(); - e.stopPropagation(); - } - }); - mocha.setup({ ui: 'tdd', timeout: typeof process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] === 'string' ? 30000 : 5000, diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 21ef972f7c4..b7e785a037d 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -166,12 +166,57 @@ function loadTestModules(opts) { }).then(loadModules); } +let currentTestTitle; + function loadTests(opts) { + //#region Unexpected Output + + const _allowedTestOutput = new Set([ + 'The vm module of Node.js is deprecated in the renderer process and will be removed.', + ]); + + const _allowedTestsWithOutput = new Set([ + 'creates a snapshot', // https://github.com/microsoft/vscode/issues/192439 + 'validates a snapshot', // https://github.com/microsoft/vscode/issues/192439 + 'cleans up old snapshots', // https://github.com/microsoft/vscode/issues/192439 + 'issue #149412: VS Code hangs when bad semantic token data is received', // https://github.com/microsoft/vscode/issues/192440 + 'issue #134973: invalid semantic tokens should be handled better', // https://github.com/microsoft/vscode/issues/192440 + 'issue #148651: VSCode UI process can hang if a semantic token with negative values is returned by language service', // https://github.com/microsoft/vscode/issues/192440 + 'issue #149130: vscode freezes because of Bracket Pair Colorization', // https://github.com/microsoft/vscode/issues/192440 + 'property limits', // https://github.com/microsoft/vscode/issues/192443 + 'Error events', // https://github.com/microsoft/vscode/issues/192443 + 'Ensure output channel is logged to', // https://github.com/microsoft/vscode/issues/192443 + 'guards calls after runs are ended' // https://github.com/microsoft/vscode/issues/192468 + ]); + + let _testsWithUnexpectedOutput = false; + + for (const consoleFn of [console.log, console.error, console.info, console.warn, console.trace, console.debug]) { + console[consoleFn.name] = function (msg) { + if (!_allowedTestOutput.has(msg) && !_allowedTestsWithOutput.has(currentTestTitle)) { + _testsWithUnexpectedOutput = true; + consoleFn.apply(console, arguments); + } + }; + } + + //#endregion + + //#region Unexpected / Loader Errors + const _unexpectedErrors = []; const _loaderErrors = []; - // collect loader errors + const _allowedTestsWithUnhandledRejections = new Set([ + // Lifecycle tests + 'onWillShutdown - join with error is handled', + 'onBeforeShutdown - veto with error is treated as veto', + 'onBeforeShutdown - final veto with error is treated as veto', + // Search tests + 'Search Model: Search reports timed telemetry on search when error is called' + ]); + loader.require.config({ onError(err) { _loaderErrors.push(err); @@ -179,18 +224,40 @@ function loadTests(opts) { } }); - // collect unexpected errors loader.require(['vs/base/common/errors'], function (errors) { - errors.setUnexpectedErrorHandler(function (err) { + + const onUnexpectedError = function (err) { + if (err.name === 'Canceled') { + return; // ignore canceled errors that are common + } + let stack = (err ? err.stack : null); if (!stack) { stack = new Error().stack; } _unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack); + }; + + process.on('uncaughtException', error => onUnexpectedError(error)); + process.on('unhandledRejection', (reason, promise) => { + onUnexpectedError(reason); + promise.catch(() => {}); }); + window.addEventListener('unhandledrejection', event => { + event.preventDefault(); // Do not log to test output, we show an error later when test ends + event.stopPropagation(); + + if (!_allowedTestsWithUnhandledRejections.has(currentTestTitle)) { + onUnexpectedError(event.reason); + } + }); + + errors.setUnexpectedErrorHandler(err => unexpectedErrorHandler(err)); }); + //#endregion + return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => { const assertCleanState = workbenchTestingModule.assertCleanState; @@ -200,24 +267,30 @@ function loadTests(opts) { }); }); - return loadTestModules(opts).then(() => { - suite('Unexpected Errors & Loader Errors', function () { - test('should not have unexpected errors', function () { - const errors = _unexpectedErrors.concat(_loaderErrors); - if (errors.length) { - errors.forEach(function (stack) { - console.error(''); - console.error(stack); - }); - assert.ok(false, errors); - } - }); + teardown(() => { - test('assertCleanState - check that registries are clean and objects are disposed at the end of test running', () => { - assertCleanState(); - }); - }); + // should not have unexpected output + if (_testsWithUnexpectedOutput) { + assert.ok(false, 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.'); + } + + // should not have unexpected errors + const errors = _unexpectedErrors.concat(_loaderErrors); + if (errors.length) { + for (const error of errors) { + console.error(`Error: Test run should not have unexpected errors:\n${error}`); + } + assert.ok(false, 'Error: Test run should not have unexpected errors.'); + } }); + + suiteTeardown(() => { // intentionally not in teardown because some tests only cleanup in suiteTeardown + + // should have cleaned up in registries + assertCleanState(); + }); + + return loadTestModules(opts); }); } @@ -329,9 +402,10 @@ function runTests(opts) { }); }); + runner.on('test', test => currentTestTitle = test.title); + if (opts.dev) { runner.on('fail', (test, err) => { - console.error(test.fullTitle()); console.error(err.stack); }); diff --git a/yarn.lock b/yarn.lock index 59f8d5ce788..cf5b0450563 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3574,10 +3574,10 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== -electron@25.8.0: - version "25.8.0" - resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.0.tgz#60c84f1f256924ac5a0aff13276b901b0c43767a" - integrity sha512-T3kC1a/3ntSaYMCVVfUUc9v7myPzi6J2GP0Ad/CyfWKDPp054dGyKxb2EEjKnxQQ7wfjsT1JTEdBG04x6ekVBw== +electron@25.8.1: + version "25.8.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.1.tgz#092fab5a833db4d9240d4d6f36218cf7ca954f86" + integrity sha512-GtcP1nMrROZfFg0+mhyj1hamrHvukfF6of2B/pcWxmWkd5FVY1NJib0tlhiorFZRzQN5Z+APLPr7aMolt7i2AQ== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" @@ -10008,10 +10008,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.3.0-dev.20230905: - version "5.3.0-dev.20230905" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.0-dev.20230905.tgz#b88de602ef4afcc3a80a9c38023df82b7529e42a" - integrity sha512-Nl9MoKWN0YYlCvQnw850L4ZgqdmqwVGCi9cAoQDw4PsqRGaWAi9HKizS9xu0q4qgKKsEKetWCZHT8dBtJTGaMg== +typescript@^5.3.0-dev.20230911: + version "5.3.0-dev.20230911" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.0-dev.20230911.tgz#7f60e82ee86e381655ddc63141408eb90b6ca31d" + integrity sha512-2iI2l7OuGvU668gBje+JQKE8bsf7SH8w8ScwUkENHCcrbaDpXa/Oqfuwq5gdFM7SfVfp5p6c8kHZRMvL+kabJg== typical@^4.0.0: version "4.0.0" @@ -10685,45 +10685,45 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-canvas@0.5.0-beta.22: - version "0.5.0-beta.22" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0-beta.22.tgz#513f0c2b7cf96073f47627b27e8965c1b1a22431" - integrity sha512-9F6ZI0DMRgffVfHkLkDwl5n8VscvCaV10tWI3skXOX7Y7Aws6OEeglkOPoU3IllofCU792kHKM4pPoToUxTltg== +xterm-addon-canvas@0.6.0-beta.19: + version "0.6.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.19.tgz#c9e01330a548fbb243d731728e70ae77e6dc8c4f" + integrity sha512-S2tZXqnAqkLA5r40gbOlciR7CLtfztn0lk/ko8Bol08pgSqEzWKF0yadYpsezHRmemvTSmyePCtOTqDrEgFQBA== -xterm-addon-image@0.6.0-beta.14: - version "0.6.0-beta.14" - resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.14.tgz#75fc3f824123183a4bbb5306e22f8b2c6966b0a6" - integrity sha512-D5Gh5JTKhHaPt1KwQNf6diF37KA4eToJw3XId1wy62tWmSqfq+QflhOGTfd+SnSQYCktU05ETzM+0tncIU62pQ== +xterm-addon-image@0.6.0-beta.21: + version "0.6.0-beta.21" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" + integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.13.0-beta.20: - version "0.13.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0-beta.20.tgz#8ddd0513e2a70fcefa325722100d2e1bfaf3b9cb" - integrity sha512-wrx6187cJ1UenGL6ZeYv3jFvRPhhENTfbC+Hv1Fnww8LmsKhcj+0+Pm6yInNjX/9hNVsNzdqKyqNeEMoykyoyA== +xterm-addon-search@0.14.0-beta.18: + version "0.14.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.18.tgz#6298b34d9c590f3e3acdd101eb297eaf8cb1f298" + integrity sha512-1CF2bPz9/vQR+q7OFgjvbBRQ0rUSkiKlwZJMnizgbKl6qx0GNg15T52J+l8zLgg8HavlF8aVps1co1A+N5PPZA== -xterm-addon-serialize@0.11.0-beta.20: - version "0.11.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.11.0-beta.20.tgz#e879b34d214761403f1081833f9221c6903bf0c3" - integrity sha512-OXnC1SATaz7kEFjFWhyv9MJaXi8yHdPjazpGLNi11h33CRTKtCQiqqPBHU87dztnXmpEX6Jw0/jr3zlyXuAmnw== +xterm-addon-serialize@0.12.0-beta.18: + version "0.12.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.18.tgz#0d9369acb49fa01f124dfff064a17f4874211f1a" + integrity sha512-RyV6iU/KRC3QN29i3iaWzm33ACbi0gMQW+LjKSFJN/XrNO1QTqfnh2VZp58G32IFG8l2sg/FUs2gXtEB5IMlhA== -xterm-addon-unicode11@0.6.0-beta.12: - version "0.6.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.6.0-beta.12.tgz#ac6df9d635325dc692e4c602e74a2fc27a09405c" - integrity sha512-9wWWf/5nFafYgq0pn9EgAWnXaXGleVxfjNOqavpLRYFv0nw42QbaYyGvnGcxyYHM5Aqx/8rYE/DDVWZBqQZdYA== +xterm-addon-unicode11@0.7.0-beta.18: + version "0.7.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.18.tgz#8e47b5be3ce5f07a136ca66919f548680da96648" + integrity sha512-5Zy2Kn7kSYQP2ItPV9HNsX2u6/XajB6uyRZ/tx5U79XjZIOMMtPLki56fiGxBOlyhIHFr96bwRlvYKZcEY1ndQ== -xterm-addon-webgl@0.16.0-beta.30: - version "0.16.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0-beta.30.tgz#820d5c65f868b14ec4177bfb8a294931a53616bf" - integrity sha512-39qPHPFmNENxcHf8/CzGHS6wzKMMegoRkHB1+scqtBhSxFaD8tX5Ye33HZIEdQ9nXe9xtr4FWVp77T+n9hdrew== +xterm-addon-webgl@0.17.0-beta.18: + version "0.17.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.18.tgz#0ce7b2ed4f1aaefff0eb59dac5c0e2fbba08d9ec" + integrity sha512-F94/+Koo98fAwVr8zFw4vYnmZPKyN6K4ZQTDM2ICozBHtiVWgR2PjhBP8covD6vXwbsnrwq5aipJLbhBMeZ60w== -xterm-headless@5.3.0-beta.61: - version "5.3.0-beta.61" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.3.0-beta.61.tgz#28654550cb572709b99ea3eb8672d4568ae141c9" - integrity sha512-yfkbPLUtKjE4K7DsZ204A1BuOKpu6Usqi6rIYWT4XRMi+LjnkTbBjGr2BSjyJ3Gmtm+cSgBD0SvRN+V3xNxbxA== +xterm-headless@5.4.0-beta.19: + version "5.4.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.19.tgz#cdbad09917bdbeeae9197c87663d603fc2794212" + integrity sha512-lLHbZ0DUBoolt4kWCchBAZDlDDdWROwIFxzG8sK389/Z7AlVWsA7kasz2TIPN+l9SC7MrhDdWIFwi1Z0eVODcg== -xterm@5.3.0-beta.61: - version "5.3.0-beta.61" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0-beta.61.tgz#a6c27d90a5314da51d80deeb32f3bd77f1e1c8f6" - integrity sha512-rJHpCc48GSpHnu0SSERynQ80D5ikvFVsqhv6JdmeONTrnAFRr134OglJRIpbi2YK8UPbV6F6Dfqm/AQh+9GZzA== +xterm@5.4.0-beta.19: + version "5.4.0-beta.19" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.19.tgz#5177e37c8e885aa5cbbb0b1972e9df7634a8aa33" + integrity sha512-eM5UmMf3ml8NIBixEwH5CKj5rgwiZhE51W8xhs7js1GIGVusG/1SL7gS6d/n9UlspnAvQtUOIqzc70x887m6jQ== y18n@^3.2.1: version "3.2.2"