diff --git a/.eslintignore b/.eslintignore index 1c49d5b8846..e3aedfea0d6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -25,10 +25,6 @@ **/src/typings/**/*.d.ts **/src/vs/*/**/*.d.ts **/src/vs/base/test/common/filters.perf.data.js -**/src/vs/css.build.js -**/src/vs/css.js **/src/vs/loader.js -**/src/vs/nls.build.js -**/src/vs/nls.js **/test/unit/assert.js **/typings/** diff --git a/.eslintrc.json b/.eslintrc.json index 9e9b1ddabfb..d86f6103a7d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -220,15 +220,11 @@ // - electron-main "when": "hasNode", "allow": [ - "@microsoft/applicationinsights-web", "@parcel/watcher", "@vscode/sqlite3", "@vscode/vscode-languagedetection", "@vscode/ripgrep", "@vscode/iconv-lite-umd", - "applicationinsights", - "@microsoft/1ds-core-js", - "@microsoft/1ds-post-js", "assert", "child_process", "console", @@ -263,6 +259,7 @@ "windows-process-tree", "worker_threads", "xterm", + "xterm-addon-canvas", "xterm-addon-search", "xterm-addon-serialize", "xterm-addon-unicode11", @@ -277,6 +274,7 @@ // imports that are allowed in all /test/ files "when": "test", "allow": [ + "vs/css.build", "assert", "sinon", "sinon-test" @@ -331,7 +329,9 @@ "vs/base/~", "vs/base/parts/*/~", "vs/platform/*/~", - "tas-client-umd" // node module allowed even in /common/ + "tas-client-umd", // node module allowed even in /common/ + "@microsoft/1ds-core-js",// node module allowed even in /common/ + "@microsoft/1ds-post-js" // node module allowed even in /common/ ] }, { @@ -445,8 +445,7 @@ }, // TODO@layers "tas-client-umd", // node module allowed even in /common/ "vscode-textmate", // node module allowed even in /common/ - "@vscode/vscode-languagedetection", // node module allowed even in /common/ - "@microsoft/applicationinsights-web" // node module allowed even in /common/ + "@vscode/vscode-languagedetection" // node module allowed even in /common/ ] }, { @@ -537,7 +536,7 @@ ] }, { - "target": "src/vs/workbench/{workbench.sandbox.main.ts,workbench.desktop.sandbox.main.ts}", + "target": "src/vs/workbench/workbench.desktop.main.ts", "layer": "electron-sandbox", "restrictions": [ "vs/base/*/~", @@ -550,26 +549,7 @@ "vs/workbench/api/~", "vs/workbench/services/*/~", "vs/workbench/contrib/*/~", - "vs/workbench/workbench.common.main", - "vs/workbench/workbench.sandbox.main" - ] - }, - { - "target": "src/vs/workbench/workbench.desktop.main.ts", - "layer": "electron-browser", - "restrictions": [ - "vs/base/*/~", - "vs/base/parts/*/~", - "vs/platform/*/~", - "vs/editor/~", - "vs/editor/contrib/*/~", - "vs/editor/editor.all", - "vs/workbench/~", - "vs/workbench/api/~", - "vs/workbench/services/*/~", - "vs/workbench/contrib/*/~", - "vs/workbench/workbench.common.main", - "vs/workbench/workbench.sandbox.main" + "vs/workbench/workbench.common.main" ] }, { @@ -577,7 +557,7 @@ "restrictions": [] }, { - "target": "src/vs/{css.d.ts,monaco.d.ts,nls.d.ts,nls.mock.ts}", + "target": "src/vs/{loader.d.ts,css.ts,css.build.ts,monaco.d.ts,nls.ts,nls.build.ts,nls.mock.ts}", "restrictions": [] }, { diff --git a/.github/classifier.json b/.github/classifier.json index 5b5625f6e93..9e7e6e26e17 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -145,7 +145,7 @@ "snippets": {"assign": ["jrieken"]}, "splitview": {"assign": ["joaomoreno"]}, "suggest": {"assign": ["jrieken"]}, - "tasks": {"assign": ["alexr00"], "accuracy": 0.85}, + "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, "telemetry": {"assign": []}, "themes": {"assign": ["aeschli"]}, "timeline": {"assign": ["lramos15"]}, diff --git a/.github/commands.json b/.github/commands.json index 68a50baae95..52c8add617c 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -15,30 +15,35 @@ "type": "label", "name": "*question", "action": "close", + "reason": "not_planned", "comment": "We closed this issue because it is a question about using VS Code rather than an issue or feature request. Please search for help on [StackOverflow](https://aka.ms/vscodestackoverflow), where the community has already answered thousands of similar questions. You may find their [guide on asking a new question](https://aka.ms/vscodestackoverflowquestion) helpful if your question has not already been asked. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", "name": "*dev-question", "action": "close", + "reason": "not_planned", "comment": "We have a great developer community [over on slack](https://aka.ms/vscode-dev-community) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" }, { "type": "label", "name": "*extension-candidate", "action": "close", + "reason": "not_planned", "comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", "name": "*not-reproducible", "action": "close", + "reason": "not_planned", "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) might help you with that.\n\nHappy Coding!" }, { "type": "label", "name": "*out-of-scope", "action": "close", + "reason": "not_planned", "comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!" }, { @@ -57,12 +62,14 @@ "type": "label", "name": "*caused-by-extension", "action": "close", + "reason": "not_planned", "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { "type": "label", "name": "*as-designed", "action": "close", + "reason": "not_planned", "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { @@ -104,6 +111,7 @@ "type": "label", "name": "*duplicate", "action": "close", + "reason": "not_planned", "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { @@ -154,7 +162,7 @@ "IllusionMH" ], "action": "updateLabels", - "addLabel": "~needs more info" + "addLabel": "~info-needed" }, { "type": "comment", @@ -165,14 +173,14 @@ "gjsjohnmurray", "IllusionMH" ], - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!" }, { "type": "comment", "name": "jsDebugLogs", "action": "updateLabels", - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠ī¸ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com" }, { @@ -185,22 +193,23 @@ "IllusionMH" ], "action": "close", + "reason": "completed", "addLabel": "unreleased" }, { "type": "label", - "name": "~needs more info", + "name": "~info-needed", "action": "updateLabels", - "addLabel": "needs more info", - "removeLabel": "~needs more info", + "addLabel": "info-needed", + "removeLabel": "~info-needed", "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, { "type": "label", - "name": "~needs version info", + "name": "~version-info-needed", "action": "updateLabels", - "addLabel": "needs more info", - "removeLabel": "~needs version info", + "addLabel": "info-needed", + "removeLabel": "~version-info-needed", "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!" }, { @@ -222,6 +231,7 @@ "type": "label", "name": "*off-topic", "action": "close", + "reason": "not_planned", "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" }, { @@ -234,6 +244,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Python extension. Please file the issue to the [Python extension repository](https://github.com/microsoft/vscode-python). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -247,6 +258,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Jupyter extension. Please file the issue to the [Jupyter extension repository](https://github.com/microsoft/vscode-jupyter). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -260,6 +272,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the C extension. Please file the issue to the [C extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -273,6 +286,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -286,6 +300,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the C++ extension. Please file the issue to the [C++ extension repository](https://github.com/microsoft/vscode-cpptools). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -299,6 +314,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the TypeScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -312,6 +328,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the TypeScript/JavaScript language service. Please file the issue to the [TypeScript repository](https://github.com/microsoft/TypeScript/). Make sure to check their [contributing guidelines](https://github.com/microsoft/TypeScript/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -325,6 +342,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the C# extension. Please file the issue to the [C# extension repository](https://github.com/OmniSharp/omnisharp-vscode.git). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -351,6 +369,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the PowerShell extension. Please file the issue to the [PowerShell extension repository](https://github.com/PowerShell/vscode-powershell). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -364,6 +383,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the LiveShare extension. Please file the issue to the [LiveShare repository](https://github.com/MicrosoftDocs/live-share). Make sure to check their [contributing guidelines](https://github.com/MicrosoftDocs/live-share/blob/master/CONTRIBUTING.md) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -377,6 +397,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Docker extension. Please file the issue to the [Docker extension repository](https://github.com/microsoft/vscode-docker). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -390,6 +411,7 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Java extension. Please file the issue to the [Java extension repository](https://github.com/redhat-developer/vscode-java). Make sure to check their [troubleshooting instructions](https://github.com/redhat-developer/vscode-java/wiki/Troubleshooting) and provide relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, @@ -403,9 +425,24 @@ "IllusionMH" ], "action": "close", + "reason": "not_planned", "addLabel": "*caused-by-extension", "comment": "It looks like this is caused by the Java Debugger extension. Please file the issue to the [Java Debugger repository](https://github.com/microsoft/vscode-java-debug). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" }, + { + "type": "comment", + "name": "extCodespaces", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "close", + "reason": "not_planned", + "addLabel": "*caused-by-extension", + "comment": "It looks like this is caused by the Codespaces extension. Please file the issue in the [Codespaces Discussion Forum](http://aka.ms/ghcs-feedback). Make sure to check their issue reporting template and provide them relevant information such as the extension version you're using. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting) for more information.\n\nHappy Coding!" + }, { "type": "comment", "name": "gifPlease", @@ -416,7 +453,7 @@ "IllusionMH" ], "action": "comment", - "addLabel": "needs more info", + "addLabel": "info-needed", "comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette).\n\nHappy coding!" }, { @@ -439,6 +476,15 @@ "type": "label", "name": "*workspace-trust-docs", "action": "close", + "reason": "not_planned", "comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know." + }, + { + "type": "label", + "name": "~verification-steps-needed", + "action": "updateLabels", + "addLabel": "verification-steps-needed", + "removeLabel": "~verification-steps-needed", + "comment": "Friendly ping! Looks like this issue requires some further steps to be verified. Please provide us with the steps necessary to verify this issue." } ] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 19314029215..5335e645320 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,5 +5,3 @@ * Ensure that the code is up-to-date with the `main` branch. * Include a description of the proposed changes and how to test them. --> - -This PR fixes # diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index a51140cc030..8a0a39315bb 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -22,8 +22,6 @@ jobs: # TODO: rename azure-pipelines/linux/xvfb.init to github-actions - name: Setup Build Environment run: | - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb sudo update-rc.d xvfb defaults @@ -41,8 +39,7 @@ jobs: uses: actions/cache@v3 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -62,10 +59,7 @@ jobs: run: yarn --frozen-lockfile --network-timeout 180000 - name: Compile and Download - run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions - - - name: Compile Integration Tests - run: yarn --cwd test/integration/browser compile + run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" - name: Run Unit Tests id: electron-unit-tests @@ -97,8 +91,7 @@ jobs: uses: actions/cache@v3 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -117,9 +110,6 @@ jobs: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 run: yarn --frozen-lockfile --network-timeout 180000 - - name: Download Playwright - run: yarn playwright-install - - name: Run Hygiene Checks run: yarn gulp hygiene @@ -163,8 +153,7 @@ jobs: uses: actions/cache@v3 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 088723285cf..d505e2b9129 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,8 +125,7 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -197,8 +196,7 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesMacOS-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} @@ -271,8 +269,7 @@ jobs: uses: actions/cache@v2 with: path: "**/node_modules" - key: ${{ runner.os }}-cacheNodeModules22-${{ steps.nodeModulesCacheKey.outputs.value }} - restore-keys: ${{ runner.os }}-cacheNodeModules22- + key: ${{ runner.os }}-cacheNodeModulesLinux-${{ steps.nodeModulesCacheKey.outputs.value }} - name: Get yarn cache directory path id: yarnCacheDirPath if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6c5d2999898..f64aa7df15c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,14 +24,14 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: javascript # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -45,4 +45,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml index bae2eeb1967..a966318ddef 100644 --- a/.github/workflows/deep-classifier-runner.yml +++ b/.github/workflows/deep-classifier-runner.yml @@ -46,7 +46,7 @@ jobs: uses: ./actions/classifier-deep/apply/apply-labels with: configPath: classifier - allowLabels: "needs more info|new release|error-telemetry|*english-please|translation-required" + allowLabels: "info-needed|new release|error-telemetry|*english-please|translation-required" appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} manifestDbConnectionString: ${{secrets.MANIFEST_DB_CONNECTION_STRING}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} diff --git a/.github/workflows/english-please.yml b/.github/workflows/english-please.yml index da8ca03ddd8..2f24b039125 100644 --- a/.github/workflows/english-please.yml +++ b/.github/workflows/english-please.yml @@ -24,6 +24,6 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" diff --git a/.github/workflows/needs-more-info-closer.yml b/.github/workflows/needs-more-info-closer.yml index 5e1f5b5c548..65805be0d51 100644 --- a/.github/workflows/needs-more-info-closer.yml +++ b/.github/workflows/needs-more-info-closer.yml @@ -1,4 +1,4 @@ -name: Needs More Info Closer +name: info-needed Closer on: schedule: - cron: 20 11 * * * # 4:20am Redmond @@ -17,12 +17,12 @@ jobs: ref: stable - name: Install Actions run: npm install --production --prefix ./actions - - name: Run Needs More Info Closer + - name: Run info-needed Closer uses: ./actions/needs-more-info-closer with: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} - label: needs more info + label: info-needed closeDays: 7 additionalTeam: "cleidigh|usernamehw|gjsjohnmurray|IllusionMH" closeComment: "This issue has been closed automatically because it needs more information and has not had recent activity. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" diff --git a/.github/workflows/no-yarn-lock-changes.yml b/.github/workflows/no-yarn-lock-changes.yml index ebd735bf7e5..300a98ad998 100644 --- a/.github/workflows/no-yarn-lock-changes.yml +++ b/.github/workflows/no-yarn-lock-changes.yml @@ -18,8 +18,9 @@ jobs: run: | echo "user: ${{ github.event.pull_request.user.login }}" echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" + echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" echo "should_run: ${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" - echo "::set-output name=should_run::${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" + echo "::set-output name=should_run::${{ !contains(fromJson('["admin", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" - name: Get file changes uses: trilom/file-changes-action@ce38c8ce2459ca3c303415eec8cb0409857b4272 if: ${{ steps.control.outputs.should_run == 'true' }} diff --git a/.github/workflows/on-label.yml b/.github/workflows/on-label.yml index 25f7a67c7a1..9771860d437 100644 --- a/.github/workflows/on-label.yml +++ b/.github/workflows/on-label.yml @@ -71,7 +71,8 @@ jobs: if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') uses: ./actions/test-plan-item-validator with: - appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + refLabel: on-testplan label: testplan-item invalidLabel: invalid-testplan-item comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved. @@ -85,6 +86,6 @@ jobs: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" diff --git a/.github/workflows/on-open.yml b/.github/workflows/on-open.yml index af70c86caa0..8fef95d9e83 100644 --- a/.github/workflows/on-open.yml +++ b/.github/workflows/on-open.yml @@ -59,6 +59,16 @@ jobs: appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} cognitiveServicesAPIKey: ${{secrets.AZURE_TEXT_TRANSLATOR_KEY}} nonEnglishLabel: "*english-please" - needsMoreInfoLabel: "needs more info" + needsMoreInfoLabel: "info-needed" translatorRequestedLabelPrefix: "translation-required-" translatorRequestedLabelColor: "c29cff" + # source of truth in ./test-plan-item-validator.yml + - name: Run Test Plan Item Validator + uses: ./actions/test-plan-item-validator + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + refLabel: on-testplan + label: testplan-item + invalidLabel: invalid-testplan-item + comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved. + diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml index 82bd093e039..d3b9284f9ae 100644 --- a/.github/workflows/test-plan-item-validator.yml +++ b/.github/workflows/test-plan-item-validator.yml @@ -3,7 +3,7 @@ on: issues: types: [edited] -# also edit in ./on-label.yml +# also edit in ./on-label.yml and ./on-open.yml jobs: main: runs-on: ubuntu-latest @@ -22,7 +22,8 @@ jobs: if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') uses: ./actions/test-plan-item-validator with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + refLabel: on-testplan label: testplan-item - appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} invalidLabel: invalid-testplan-item comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved. diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..0cf077e6b4c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16.14 diff --git a/.vscode/launch.json b/.vscode/launch.json index 236fc3de8c3..f858dc019fe 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,7 +24,7 @@ ] }, { - "type": "pwa-chrome", + "type": "chrome", "request": "attach", "name": "Attach to Shared Process", "timeout": 30000, @@ -92,6 +92,23 @@ "order": 6 } }, + { + "type": "extensionHost", + "request": "launch", + "name": "VS Code Configuration Editing Tests", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extensions/configuration-editing", + "--extensionTestsPath=${workspaceFolder}/extensions/configuration-editing/out/test" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "presentation": { + "group": "5_tests", + "order": 6 + } + }, { "type": "extensionHost", "request": "launch", @@ -185,25 +202,7 @@ } }, { - "type": "extensionHost", - "request": "launch", - "name": "VS Code Custom Editor Tests", - "runtimeExecutable": "${execPath}", - "args": [ - "${workspaceFolder}/extensions/vscode-custom-editor-tests/test-workspace", - "--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-custom-editor-tests", - "--extensionTestsPath=${workspaceFolder}/extensions/vscode-custom-editor-tests/out/test" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "presentation": { - "group": "5_tests", - "order": 6 - } - }, - { - "type": "pwa-chrome", + "type": "chrome", "request": "attach", "name": "Attach to VS Code", "browserAttachLocation": "workspace", @@ -217,7 +216,7 @@ "perScriptSourcemaps": "yes" }, { - "type": "pwa-chrome", + "type": "chrome", "request": "launch", "name": "Launch VS Code Internal", "windows": { @@ -259,7 +258,7 @@ } }, { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "VS Code Server (Web)", "runtimeExecutable": "${workspaceFolder}/scripts/code-server.sh", @@ -275,7 +274,7 @@ } }, { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "Main Process", "attachSimplePort": 5875, @@ -296,7 +295,7 @@ } }, { - "type": "pwa-chrome", + "type": "chrome", "request": "launch", "outFiles": [], "perScriptSourcemaps": "yes", @@ -309,7 +308,7 @@ } }, { - "type": "pwa-msedge", + "type": "msedge", "request": "launch", "outFiles": [], "perScriptSourcemaps": "yes", @@ -404,7 +403,7 @@ } }, { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "Run Unit Tests", "program": "${workspaceFolder}/test/unit/electron/index.js", @@ -434,7 +433,7 @@ } }, { - "type": "pwa-node", + "type": "node", "request": "launch", "name": "Run Unit Tests For Current File", "program": "${workspaceFolder}/test/unit/electron/index.js", diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 3120d4ad8fc..d02651f621e 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"June 2022\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"August 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 48195c79000..d637ade8f20 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\r\n\r\n$MILESTONE=milestone:\"May 2022\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"July 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/inbox.github-issues b/.vscode/notebooks/inbox.github-issues index ad451a6f91a..10d7fd63e54 100644 --- a/.vscode/notebooks/inbox.github-issues +++ b/.vscode/notebooks/inbox.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$inbox -label:\"needs more info\" sort:created-desc" + "value": "$inbox -label:\"info-needed\" sort:created-desc" }, { "kind": 2, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 4ce504b017f..25b9625ef31 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"May 2022\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"July 2022\"\n\n$MINE=assignee:@me" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:needs-triage -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index f596c6e2e1c..f3c7a9c7c91 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce\n\n// current milestone name\n$milestone=milestone:\"June 2022\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce\n\n// current milestone name\n$milestone=milestone:\"July 2022\"" }, { "kind": 1, @@ -92,7 +92,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"needs more info\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" + "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" }, { "kind": 1, @@ -102,7 +102,7 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"needs more info\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-RTL -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:engineering -label:error-list -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" + "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -label:editor-autoclosing -label:editor-autoindent -label:editor-bracket-matching -label:editor-clipboard -label:editor-code-actions -label:editor-color-picker -label:editor-columnselect -label:editor-commands -label:editor-comments -label:editor-contrib -label:editor-core -label:editor-drag-and-drop -label:editor-error-widget -label:editor-find -label:editor-folding -label:editor-highlight -label:editor-hover -label:editor-indent-detection -label:editor-indent-guides -label:editor-input -label:editor-input-IME -label:editor-insets -label:editor-minimap -label:editor-multicursor -label:editor-parameter-hints -label:editor-render-whitespace -label:editor-rendering -label:editor-RTL -label:editor-scrollbar -label:editor-symbols -label:editor-synced-region -label:editor-textbuffer -label:editor-theming -label:editor-wordnav -label:editor-wrapping -label:emmet -label:engineering -label:error-list -label:extension-host -label:extension-recommendations -label:extensions -label:extensions-development -label:file-decorations -label:file-encoding -label:file-explorer -label:file-glob -label:file-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -label:scm -label:screencast-mode -label:search -label:search-api -label:search-editor -label:search-replace -label:semantic-tokens -label:settings-editor -label:settings-sync -label:settings-sync-server -label:shared-process -label:simple-file-dialog -label:smart-select -label:snap -label:snippets -label:splitview -label:suggest -label:sync-error-handling -label:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -label:workbench-editors -label:workbench-electron -label:workbench-feedback -label:workbench-history -label:workbench-hot-exit -label:workbench-hover -label:workbench-launch -label:workbench-link -label:workbench-multiroot -label:workbench-notifications -label:workbench-os-integration -label:workbench-rapid-render -label:workbench-run-as-admin -label:workbench-state -label:workbench-status -label:workbench-tabs -label:workbench-touchbar -label:workbench-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open label:\"needs more info\"" + "value": "$repos assignee:@me is:open label:\"info-needed\"" }, { "kind": 1, diff --git a/.vscode/settings.json b/.vscode/settings.json index e698d02574e..71bded80a79 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,6 +73,7 @@ ], "git.branchProtectionPrompt": "alwaysCommitToNewBranch", "git.branchRandomName.enable": true, + "git.mergeEditor": true, "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" }, diff --git a/.yarnrc b/.yarnrc index c6a9765f61f..afa220d0f75 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,4 +1,4 @@ disturl "https://electronjs.org/headers" -target "18.3.2" +target "19.0.12" runtime "electron" build_from_source "true" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 0fca6a19272..5c36a652b4b 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -7,7 +7,7 @@ This project incorporates components from the projects listed below. The origina 1. atom/language-clojure version 0.22.8 (https://github.com/atom/language-clojure) 2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) -3. atom/language-css version 0.45.0 (https://github.com/atom/language-css) +3. atom/language-css version 0.45.1 (https://github.com/atom/language-css) 4. atom/language-java version 0.32.1 (https://github.com/atom/language-java) 5. atom/language-sass version 0.62.1 (https://github.com/atom/language-sass) 6. atom/language-shellscript version 0.28.2 (https://github.com/atom/language-shellscript) @@ -30,50 +30,53 @@ This project incorporates components from the projects listed below. The origina 23. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 24. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) 25. James-Yu/LaTeX-Workshop version 8.19.1 (https://github.com/James-Yu/LaTeX-Workshop) -26. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) -27. jeff-hykin/cpp-textmate-grammar version 1.15.6 (https://github.com/jeff-hykin/cpp-textmate-grammar) -28. jlelong/vscode-latex-basics version 1.3.0 (https://github.com/jlelong/vscode-latex-basics) -29. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) -30. JuliaEditorSupport/atom-language-julia version 0.22.1 (https://github.com/JuliaEditorSupport/atom-language-julia) -31. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) -32. language-docker (https://github.com/moby/moby) -33. language-less version 0.34.2 (https://github.com/atom/language-less) -34. language-php version 0.48.1 (https://github.com/atom/language-php) -35. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) -36. marked version 4.0.16 (https://github.com/markedjs/marked) -37. mdn-data version 1.1.12 (https://github.com/mdn/data) -38. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) -39. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) -40. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) -41. microsoft/vscode-mssql version 1.10.1 (https://github.com/microsoft/vscode-mssql) -42. mmims/language-batchfile version 0.7.6 (https://github.com/mmims/language-batchfile) -43. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) -44. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) -45. rust-syntax version 0.5.0 (https://github.com/dustypomerleau/rust-syntax) -46. semver version 5.5.0 (https://github.com/npm/node-semver) -47. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) -48. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) -49. sumneko/lua.tmbundle version 1.0.0 (https://github.com/sumneko/lua.tmbundle) -50. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -51. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -52. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -53. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -54. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -55. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -56. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -57. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -58. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -59. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -60. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -61. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -62. trond-snekvik/vscode-rst version 1.5.1 (https://github.com/trond-snekvik/vscode-rst) -63. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) -64. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) -65. Unicode version 12.0.0 (https://home.unicode.org/) -66. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) -67. vscode-logfile-highlighter version 2.11.0 (https://github.com/emilast/vscode-logfile-highlighter) -68. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) -69. Web Background Synchronization (https://github.com/WICG/background-sync) +26. jeff-hykin/better-c-syntax version 1.13.2 (https://github.com/jeff-hykin/better-c-syntax) +27. jeff-hykin/better-cpp-syntax version 1.15.18 (https://github.com/jeff-hykin/better-cpp-syntax) +28. jeff-hykin/better-objc-syntax version 0.2.0 (https://github.com/jeff-hykin/better-objc-syntax) +29. jeff-hykin/better-objcpp-syntax version 0.1.0 (https://github.com/jeff-hykin/better-objcpp-syntax) +30. jlelong/vscode-latex-basics version 1.3.0 (https://github.com/jlelong/vscode-latex-basics) +31. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) +32. JuliaEditorSupport/atom-language-julia version 0.22.1 (https://github.com/JuliaEditorSupport/atom-language-julia) +33. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) +34. language-docker (https://github.com/moby/moby) +35. language-less version 0.34.2 (https://github.com/atom/language-less) +36. language-php version 0.48.1 (https://github.com/atom/language-php) +37. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) +38. marked version 4.0.16 (https://github.com/markedjs/marked) +39. mdn-data version 1.1.12 (https://github.com/mdn/data) +40. microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/microsoft/TypeScript-TmLanguage) +41. microsoft/vscode-JSON.tmLanguage (https://github.com/microsoft/vscode-JSON.tmLanguage) +42. microsoft/vscode-markdown-tm-grammar version 1.0.0 (https://github.com/microsoft/vscode-markdown-tm-grammar) +43. microsoft/vscode-mssql version 1.16.0 (https://github.com/microsoft/vscode-mssql) +44. mmims/language-batchfile version 0.7.6 (https://github.com/mmims/language-batchfile) +45. NVIDIA/cuda-cpp-grammar (https://github.com/NVIDIA/cuda-cpp-grammar) +46. PowerShell/EditorSyntax version 1.0.0 (https://github.com/PowerShell/EditorSyntax) +47. rust-syntax version 0.5.0 (https://github.com/dustypomerleau/rust-syntax) +48. semver version 5.5.0 (https://github.com/npm/node-semver) +49. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +50. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage) +51. sumneko/lua.tmbundle version 1.0.0 (https://github.com/sumneko/lua.tmbundle) +52. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +53. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +54. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +55. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +56. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +57. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +58. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +59. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +60. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +61. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +62. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +63. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +64. trond-snekvik/vscode-rst version 1.5.1 (https://github.com/trond-snekvik/vscode-rst) +65. TypeScript-TmLanguage version 0.1.8 (https://github.com/microsoft/TypeScript-TmLanguage) +66. TypeScript-TmLanguage version 1.0.0 (https://github.com/microsoft/TypeScript-TmLanguage) +67. Unicode version 12.0.0 (https://home.unicode.org/) +68. vscode-codicons version 0.0.14 (https://github.com/microsoft/vscode-codicons) +69. vscode-logfile-highlighter version 2.15.0 (https://github.com/emilast/vscode-logfile-highlighter) +70. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +71. vscode-win32-app-container-tokens (https://github.com/microsoft/vscode-win32-app-container-tokens) +72. Web Background Synchronization (https://github.com/WICG/background-sync) %% atom/language-clojure NOTICES AND INFORMATION BEGIN HERE @@ -1208,7 +1211,7 @@ SOFTWARE. ========================================= END OF James-Yu/LaTeX-Workshop NOTICES AND INFORMATION -%% jeff-hykin/cpp-textmate-grammar NOTICES AND INFORMATION BEGIN HERE +%% jeff-hykin/better-c-syntax NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -1232,7 +1235,85 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF jeff-hykin/cpp-textmate-grammar NOTICES AND INFORMATION +END OF jeff-hykin/better-c-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-cpp-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2019 Jeff Hykin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jeff-hykin/better-cpp-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-objc-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2019 Jeff Hykin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jeff-hykin/better-objc-syntax NOTICES AND INFORMATION + +%% jeff-hykin/better-objcpp-syntax NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) 2019 Jeff Hykin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +========================================= +END OF jeff-hykin/better-objcpp-syntax NOTICES AND INFORMATION %% jlelong/vscode-latex-basics NOTICES AND INFORMATION BEGIN HERE ========================================= @@ -3123,6 +3204,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO ========================================= END OF vscode-swift NOTICES AND INFORMATION +%% vscode-win32-app-container-tokens NOTICES AND INFORMATION BEGIN HERE +========================================= +MIT License + +Copyright (c) Microsoft Corporation. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE +========================================= +END OF vscode-win32-app-container-tokens NOTICES AND INFORMATION + %% Web Background Synchronization NOTICES AND INFORMATION BEGIN HERE ========================================= Apache License diff --git a/build/.cachesalt b/build/.cachesalt index 11b63081f5a..4f9c24c0470 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2022-06-10T10:20:54.664Z +2022-08-01T11:24:47.411Z diff --git a/build/.moduleignore b/build/.moduleignore index a2e1b715e1b..438f9b575d1 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -122,10 +122,6 @@ vscode-windows-ca-certs/**/* node-addon-api/**/* prebuild-install/**/* -@microsoft/applicationinsights*/** -@microsoft/dynamicproto-js/** -!@microsoft/applicationinsights-web/dist/applicationinsights-web.min.js - # other node modules **/docs/** diff --git a/build/.webignore b/build/.webignore index f55882ba9d4..1a5b3ee2c10 100644 --- a/build/.webignore +++ b/build/.webignore @@ -20,6 +20,9 @@ vscode-textmate/webpack.config.js xterm/src/** +xterm-addon-canvas/src/** +xterm-addon-canvas/out/** + xterm-addon-search/src/** xterm-addon-search/out/** xterm-addon-search/fixtures/** @@ -33,6 +36,3 @@ xterm-addon-webgl/out/** # This makes sure the model is included in the package !@vscode/vscode-languagedetection/model/** -@microsoft/applicationinsights*/** -@microsoft/dynamicproto-js/** -!@microsoft/applicationinsights-web/dist/applicationinsights-web.min.js \ No newline at end of file diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js new file mode 100644 index 00000000000..abc90f6ed2c --- /dev/null +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js @@ -0,0 +1,15 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +const crypto = require("crypto"); +const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); +const shasum = crypto.createHash('sha1'); +for (const ext of productjson.builtInExtensions) { + shasum.update(`${ext.name}@${ext.version}`); +} +process.stdout.write(shasum.digest('hex')); diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts new file mode 100644 index 00000000000..f0554361607 --- /dev/null +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; + +const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); +const shasum = crypto.createHash('sha1'); + +for (const ext of productjson.builtInExtensions) { + shasum.update(`${ext.name}@${ext.version}`); +} + +process.stdout.write(shasum.digest('hex')); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/build/azure-pipelines/common/computeNodeModulesCacheKey.js index b5e293d7e64..6cfa9323e71 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index 2886b28b438..751f928da9f 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index 57207e25cfe..67a40357dcd 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const crypto = require("crypto"); diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index 769b50e079d..c2af09db8a2 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import { Readable } from 'stream'; import * as crypto from 'crypto'; diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js index 57bb87fedcf..afff1f111b3 100644 --- a/build/azure-pipelines/common/createBuild.js +++ b/build/azure-pipelines/common/createBuild.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const identity_1 = require("@azure/identity"); const cosmos_1 = require("@azure/cosmos"); diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index 8b1db94c412..512f3610116 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; diff --git a/build/azure-pipelines/common/listNodeModules.js b/build/azure-pipelines/common/listNodeModules.js index 540569c79b5..644aa83d552 100644 --- a/build/azure-pipelines/common/listNodeModules.js +++ b/build/azure-pipelines/common/listNodeModules.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); diff --git a/build/azure-pipelines/common/listNodeModules.ts b/build/azure-pipelines/common/listNodeModules.ts index 2ed6294477a..aca461f8b5f 100644 --- a/build/azure-pipelines/common/listNodeModules.ts +++ b/build/azure-pipelines/common/listNodeModules.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import * as path from 'path'; diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js index 26dacf4d731..9e96781eb67 100644 --- a/build/azure-pipelines/common/releaseBuild.js +++ b/build/azure-pipelines/common/releaseBuild.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const identity_1 = require("@azure/identity"); const cosmos_1 = require("@azure/cosmos"); diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index 064ac639e60..3b163b6b619 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { ClientSecretCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js index bd78d195abb..9c64a9b3226 100644 --- a/build/azure-pipelines/common/retry.js +++ b/build/azure-pipelines/common/retry.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.retry = void 0; async function retry(fn) { diff --git a/build/azure-pipelines/common/retry.ts b/build/azure-pipelines/common/retry.ts index 0c766771da8..ed232245dec 100644 --- a/build/azure-pipelines/common/retry.ts +++ b/build/azure-pipelines/common/retry.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - export async function retry(fn: () => Promise): Promise { let lastError: Error | undefined; diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml index c874c4deeac..72fd19ab27d 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml @@ -1,7 +1,4 @@ steps: - - checkout: self - fetchDepth: 1 - - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -11,35 +8,107 @@ steps: inputs: azureSubscription: "vscode-builds-subscription" KeyVaultName: vscode - SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password" + SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" + + - script: | + set -e + cat << EOF > ~/.netrc + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + displayName: Prepare tooling + + - script: | + set -e + git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF + echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" + git checkout FETCH_HEAD + condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) + displayName: Checkout override commit + + - script: | + set -e + git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") + displayName: Merge distro + + - script: | + mkdir -p .build + node build/azure-pipelines/common/computeNodeModulesCacheKey.js x64 $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + displayName: Prepare yarn cache flags - task: Cache@2 inputs: - key: "buildNodeModules | $(Agent.OS) | $(VSCODE_ARCH) | build/yarn.lock" - path: build/node_modules - cacheHitVar: BUILD_NODE_MODULES_RESTORED - displayName: Restore build node_modules cache + key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + + - script: | + set -e + tar -xzf .build/node_modules_cache/cache.tgz + displayName: Extract node_modules cache + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + npm install -g node-gyp@latest + node-gyp --version + displayName: Update node-gyp + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: | set -e npx https://aka.ms/enablesecurefeed standAlone timeoutInMinutes: 5 retryCountOnTaskFailure: 3 - condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'), ne(variables.BUILD_NODE_MODULES_RESTORED, 'true')) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) displayName: Switch to Terrapin packages - script: | set -e + export npm_config_arch=$(VSCODE_ARCH) + export npm_config_node_gyp=$(which node-gyp) + for i in {1..3}; do # try 3 times, for Terrapin - yarn --cwd build --frozen-lockfile --check-files && break + yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 exit 1 fi echo "Yarn failed $i, trying again..." done - displayName: Install build dependencies - condition: and(succeeded(), ne(variables.BUILD_NODE_MODULES_RESTORED, 'true')) + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive - download: current artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 89ee393e6a5..1094b41ca21 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -14,30 +16,49 @@ steps: displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + - script: | + set -e + yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium & Webkit) - timeoutInMinutes: 30 + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium & Webkit) + timeoutInMinutes: 30 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | set -e yarn gulp \ + compile-extension:configuration-editing \ compile-extension:css-language-features-server \ compile-extension:emmet \ compile-extension:git \ @@ -45,49 +66,53 @@ steps: compile-extension:html-language-features-server \ compile-extension:ipynb \ compile-extension:json-language-features-server \ + compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ - compile-extension:vscode-custom-editor-tests \ compile-extension:vscode-notebook-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser webkit - displayName: Run integration tests (Browser, Webkit) - timeoutInMinutes: 20 + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser webkit + displayName: Run integration tests (Browser, Webkit) + timeoutInMinutes: 20 + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | @@ -97,35 +122,44 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) + - script: | + set -e + yarn smoketest-no-compile --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - yarn gulp compile-extension:vscode-test-resolver - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | set -e ps -ef @@ -147,7 +181,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -163,7 +196,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build/logs diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 1b8cfef6737..ebc7104d6ce 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -38,6 +38,7 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js x64 $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags - task: Cache@2 @@ -47,10 +48,67 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - script: | set -e tar -xzf .build/node_modules_cache/cache.tgz displayName: Extract node_modules cache + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + npm install -g node-gyp@latest + node-gyp --version + displayName: Update node-gyp + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + npx https://aka.ms/enablesecurefeed standAlone + timeoutInMinutes: 5 + retryCountOnTaskFailure: 3 + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) + displayName: Switch to Terrapin packages + + - script: | + set -e + export npm_config_arch=$(VSCODE_ARCH) + export npm_config_node_gyp=$(which node-gyp) + + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive - script: | set -e diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index e8a1c70160d..e82a7cfa2e6 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -23,16 +28,18 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -65,6 +72,7 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags - task: Cache@2 @@ -74,19 +82,18 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - script: | set -e tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - - script: | - set -e - npm install -g node-gyp@latest - node-gyp --version - displayName: Update node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | set -e npx https://aka.ms/enablesecurefeed standAlone @@ -115,6 +122,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -130,11 +144,12 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci - displayName: Build client + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + displayName: Build client - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -142,17 +157,26 @@ steps: node build/azure-pipelines/mixin --server displayName: Mix in server quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci - displayName: Build Server + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci + displayName: Build Server + + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp "transpile-client" "transpile-extensions" + displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-darwin-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} diff --git a/build/azure-pipelines/linux/product-build-alpine.yml b/build/azure-pipelines/linux/product-build-alpine.yml index 3aef7279243..c60f16d0804 100644 --- a/build/azure-pipelines/linux/product-build-alpine.yml +++ b/build/azure-pipelines/linux/product-build-alpine.yml @@ -58,6 +58,7 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags - task: Cache@2 @@ -67,6 +68,12 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - script: | set -e tar -xzf .build/node_modules_cache/cache.tgz @@ -98,6 +105,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt diff --git a/build/azure-pipelines/linux/product-build-linux-client-test.yml b/build/azure-pipelines/linux/product-build-linux-client-test.yml index b74eb6a144c..31d477e93aa 100644 --- a/build/azure-pipelines/linux/product-build-linux-client-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-client-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -13,43 +15,74 @@ steps: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" displayName: Download Electron and Playwright - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - ELECTRON_ROOT=.build/electron - sudo chown root $APP_ROOT/chrome-sandbox - sudo chown root $ELECTRON_ROOT/chrome-sandbox - sudo chmod 4755 $APP_ROOT/chrome-sandbox - sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox - stat $APP_ROOT/chrome-sandbox - stat $ELECTRON_ROOT/chrome-sandbox - displayName: Change setuid helper binary permission - - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e - ./scripts/test.sh --build --tfs "Unit Tests" - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + displayName: Setup build environment - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e - yarn test-node --build - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + ELECTRON_ROOT=.build/electron + sudo chown root $APP_ROOT/chrome-sandbox + sudo chown root $ELECTRON_ROOT/chrome-sandbox + sudo chmod 4755 $APP_ROOT/chrome-sandbox + sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox + stat $APP_ROOT/chrome-sandbox + stat $ELECTRON_ROOT/chrome-sandbox + displayName: Change setuid helper binary permission - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" - displayName: Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + ./scripts/test.sh --build --tfs "Unit Tests" + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - script: | + set -e + yarn test-node --build + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - script: | + set -e + DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - script: | set -e yarn gulp \ + compile-extension:configuration-editing \ compile-extension:css-language-features-server \ compile-extension:emmet \ compile-extension:git \ @@ -57,51 +90,69 @@ steps: compile-extension:html-language-features-server \ compile-extension:ipynb \ compile-extension:json-language-features-server \ + compile-extension:markdown-language-features-server \ compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ - compile-extension:vscode-custom-editor-tests \ compile-extension:vscode-notebook-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser chromium - displayName: Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 + - script: | + set -e + ./scripts/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - ./scripts/test-remote-integration.sh - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - script: | + set -e + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + ./scripts/test-web-integration.sh --browser chromium + displayName: Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + ./scripts/test-remote-integration.sh + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | @@ -113,33 +164,55 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" - timeoutInMinutes: 20 - displayName: Run smoke tests (Browser, Chromium) + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --tracing --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Electron) + - script: | + set -e + yarn smoketest-no-compile --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - yarn gulp compile-extension:vscode-test-resolver - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: Run smoke tests (Remote) + - script: | + set -e + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + yarn smoketest-no-compile --remote --tracing + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + yarn smoketest-no-compile --tracing --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Electron) + + - script: | + set -e + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + timeoutInMinutes: 20 + displayName: Run smoke tests (Browser, Chromium) + + - script: | + set -e + yarn gulp compile-extension:vscode-test-resolver + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: Run smoke tests (Remote) - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - script: | set -e ps -ef @@ -163,7 +236,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -179,7 +251,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build/logs diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index ed4d853230b..a2deeb9f5a9 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -23,33 +28,37 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - task: DownloadPipelineArtifact@2 - inputs: - artifact: reh_node_modules-$(VSCODE_ARCH) - path: $(Build.ArtifactStagingDirectory) - displayName: Download server build dependencies - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: reh_node_modules-$(VSCODE_ARCH) + path: $(Build.ArtifactStagingDirectory) + displayName: Download server build dependencies + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) - - script: | - set -e - # Start X server - /etc/init.d/xvfb start - # Start dbus session - DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) - echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" - displayName: Setup system services - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + # Start X server + /etc/init.d/xvfb start + # Start dbus session + DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) + echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" + displayName: Setup system services + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -82,14 +91,30 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - task: Cache@2 + inputs: + key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: Cache@2 + inputs: + key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + - task: Cache@2 inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions - script: | set -e @@ -171,10 +196,18 @@ steps: - script: | set -e - rm -rf remote/node_modules - tar -xzf $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz --directory $(Build.SourcesDirectory)/remote - displayName: Extract server node_modules output - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + rm -rf remote/node_modules + tar -xzf $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz --directory $(Build.SourcesDirectory)/remote + displayName: Extract server node_modules output + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) - script: | set -e @@ -190,11 +223,12 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci - displayName: Build + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci + displayName: Build - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | @@ -202,17 +236,26 @@ steps: node build/azure-pipelines/mixin --server displayName: Mix in server quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci - displayName: Build Server + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci + displayName: Build Server + + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + yarn gulp "transpile-client" "transpile-extensions" + displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-linux-client-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} diff --git a/build/azure-pipelines/mixin.js b/build/azure-pipelines/mixin.js index 769ac72a9ee..273aeda9fc2 100644 --- a/build/azure-pipelines/mixin.js +++ b/build/azure-pipelines/mixin.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const json = require("gulp-json-editor"); const buffer = require('gulp-buffer'); diff --git a/build/azure-pipelines/mixin.ts b/build/azure-pipelines/mixin.ts index ccd98d6e773..aec694a61df 100644 --- a/build/azure-pipelines/mixin.ts +++ b/build/azure-pipelines/mixin.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as json from 'gulp-json-editor'; const buffer = require('gulp-buffer'); import * as filter from 'gulp-filter'; diff --git a/build/azure-pipelines/product-build-pr-cache.yml b/build/azure-pipelines/product-build-pr-cache.yml new file mode 100644 index 00000000000..042325394d3 --- /dev/null +++ b/build/azure-pipelines/product-build-pr-cache.yml @@ -0,0 +1,73 @@ +steps: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - script: | + mkdir -p .build + node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + displayName: Prepare yarn cache flags + + - task: Cache@2 + inputs: + key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + + - script: | + set -e + tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + npx https://aka.ms/enablesecurefeed standAlone + timeoutInMinutes: 5 + retryCountOnTaskFailure: 3 + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) + displayName: Switch to Terrapin packages + + - script: | + set -e + for i in {1..3}; do # try 3 times, for Terrapin + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index c8e23162f07..8362da25ee5 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -6,15 +6,6 @@ pr: branches: include: ["main", "release/*"] -resources: - containers: - - container: centos7-devtoolset8-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-x64 - options: --user 0:0 --cap-add SYS_ADMIN - - container: vscode-bionic-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-x64 - options: --user 0:0 --cap-add SYS_ADMIN - variables: - name: Codeql.SkipTaskAutoInjection value: true @@ -22,6 +13,8 @@ variables: value: true - name: ENABLE_TERRAPIN value: false + - name: VSCODE_CIBUILD + value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_PUBLISH value: false - name: VSCODE_QUALITY @@ -29,173 +22,168 @@ variables: - name: VSCODE_STEP_ON_IT value: false -stages: - - stage: Compile - jobs: - - job: Compile - pool: vscode-1es-vscode-linux-18.04 - variables: - VSCODE_ARCH: x64 - steps: - - template: product-compile.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} +jobs: + - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: + - job: Compile + displayName: Compile & Hygiene + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-compile.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - stage: LinuxServerDependencies - dependsOn: [] - pool: vscode-1es-vscode-linux-18.04 - jobs: - - job: x64 - container: centos7-devtoolset8-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: linux/product-build-linux-server.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - job: Linuxx64UnitTest + displayName: Linux (Unit Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - - stage: Windows - dependsOn: - - Compile - pool: vscode-1es-vscode-windows-2019 - jobs: - - job: WindowsUnitTests - displayName: Unit Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsIntegrationTests - displayName: Integration Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsSmokeTests - displayName: Smoke Tests - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64IntegrationTest + displayName: Linux (Integration Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false - - stage: Linux - dependsOn: - - Compile - - LinuxServerDependencies - pool: vscode-1es-vscode-linux-18.04 - jobs: - - job: Linuxx64UnitTest - displayName: Unit Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64IntegrationTest - displayName: Integration Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64SmokeTest - displayName: Smoke Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64SmokeTest + displayName: Linux (Smoke Tests) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - - stage: macOS - dependsOn: - - Compile - pool: - vmImage: macOS-latest - variables: - BUILDSECMON_OPT_IN: true - jobs: - - job: macOSUnitTest - displayName: Unit Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSIntegrationTest - displayName: Integration Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSSmokeTest - displayName: Smoke Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - job: Linuxx64MaintainNodeModulesCache + displayName: Linux (Maintain node_modules cache) + pool: vscode-1es-vscode-linux-20.04 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + steps: + - template: product-build-pr-cache.yml + + # - job: macOSUnitTest + # displayName: macOS (Unit Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSIntegrationTest + # displayName: macOS (Integration Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: macOSSmokeTest + # displayName: macOS (Smoke Tests) + # pool: + # vmImage: macOS-latest + # timeoutInMinutes: 60 + # variables: + # BUILDSECMON_OPT_IN: true + # VSCODE_ARCH: x64 + # steps: + # - template: darwin/product-build-darwin.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true + + # - job: WindowsUnitTests + # displayName: Windows (Unit Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: true + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsIntegrationTests + # displayName: Windows (Integration Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: true + # VSCODE_RUN_SMOKE_TESTS: false + # - job: WindowsSmokeTests + # displayName: Windows (Smoke Tests) + # pool: vscode-1es-vscode-windows-2019 + # timeoutInMinutes: 60 + # variables: + # VSCODE_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 46402a8b7f0..958203ec56d 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -108,9 +108,11 @@ variables: - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX - value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true), eq(parameters.VSCODE_BUILD_WEB, true)) }} + value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_MACOS value: ${{ or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }} + - name: VSCODE_BUILD_STAGE_WEB + value: ${{ eq(parameters.VSCODE_BUILD_WEB, true) }} - name: VSCODE_CIBUILD value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_PUBLISH @@ -176,45 +178,45 @@ stages: pool: vscode-1es-windows jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: WindowsUnitTests - displayName: Unit Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsIntegrationTests - displayName: Integration Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsSmokeTests - displayName: Smoke Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - job: WindowsUnitTests + displayName: Unit Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsIntegrationTests + displayName: Integration Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsSmokeTests + displayName: Smoke Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32, true)) }}: - job: Windows @@ -291,51 +293,51 @@ stages: pool: vscode-1es-linux jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: Linuxx64UnitTest - displayName: Unit Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64IntegrationTest - displayName: Integration Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64SmokeTest - displayName: Smoke Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - job: Linuxx64UnitTest + displayName: Unit Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64IntegrationTest + displayName: Integration Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64SmokeTest + displayName: Smoke Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux-client.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: - job: Linuxx64 @@ -430,13 +432,6 @@ stages: steps: - template: linux/product-build-alpine.yml - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WEB, true)) }}: - - job: LinuxWeb - variables: - VSCODE_ARCH: x64 - steps: - - template: web/product-build-web.yml - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: - stage: macOS dependsOn: @@ -447,62 +442,8 @@ stages: BUILDSECMON_OPT_IN: true jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: macOSUnitTest - displayName: Unit Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSIntegrationTest - displayName: Integration Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSSmokeTest - displayName: Smoke Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: - - job: macOS - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - - ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}: - - job: macOSTest + - job: macOSUnitTest + displayName: Unit Tests timeoutInMinutes: 90 variables: VSCODE_ARCH: x64 @@ -511,19 +452,73 @@ stages: parameters: VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - - job: macOSSign - dependsOn: - - macOS + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSIntegrationTest + displayName: Integration Tests timeoutInMinutes: 90 variables: VSCODE_ARCH: x64 steps: - - template: darwin/product-build-darwin-sign.yml + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSSmokeTest + displayName: Smoke Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: + - job: macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + + - ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}: + - job: macOSTest + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + + - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: + - job: macOSSign + dependsOn: + - macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin-sign.yml - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - job: macOSARM64 @@ -570,6 +565,19 @@ stages: steps: - template: darwin/product-build-darwin-sign.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: + - stage: Web + dependsOn: + - Compile + pool: vscode-1es-linux + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}: + - job: Web + variables: + VSCODE_ARCH: x64 + steps: + - template: web/product-build-web.yml + - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), ne(variables['VSCODE_PUBLISH'], 'false')) }}: - stage: Publish dependsOn: diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 1fd9b0441de..2b5bdc5b53a 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -46,6 +46,7 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags # using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers @@ -56,6 +57,13 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + # Cache built-in extensions to avoid GH rate limits. + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - script: | set -e tar -xzf .build/node_modules_cache/cache.tgz @@ -94,6 +102,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -116,12 +131,13 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Compile & Hygiene - - script: | - set -e - yarn --cwd test/smoke compile - yarn --cwd test/integration/browser compile - displayName: Compile test suites - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + yarn --cwd test/smoke compile + yarn --cwd test/integration/browser compile + displayName: Compile test suites + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: AzureCLI@2 @@ -151,16 +167,18 @@ steps: ./build/azure-pipelines/common/extract-telemetry.sh displayName: Extract Telemetry - - script: | - set -e - tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out - displayName: Compress compilation artifact + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e + tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out + displayName: Compress compilation artifact - - task: PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz - artifactName: Compilation - displayName: Publish compilation artifact + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: PublishPipelineArtifact@1 + inputs: + targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz + artifactName: Compilation + displayName: Publish compilation artifact - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | diff --git a/build/azure-pipelines/product-publish.ps1 b/build/azure-pipelines/product-publish.ps1 index 5abfed48dca..5006ec61a30 100644 --- a/build/azure-pipelines/product-publish.ps1 +++ b/build/azure-pipelines/product-publish.ps1 @@ -46,6 +46,7 @@ $stages = @( if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' } if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' } if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' } + if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' } ) do { diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 4d711aba120..80076fd666d 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -109,6 +109,7 @@ steps: if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' } if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' } if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' } + if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' } ) Write-Host "Stages to check: $stages" diff --git a/build/azure-pipelines/publish-types/check-version.js b/build/azure-pipelines/publish-types/check-version.js index b45ad3f4cc0..34779085a79 100644 --- a/build/azure-pipelines/publish-types/check-version.js +++ b/build/azure-pipelines/publish-types/check-version.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const cp = require("child_process"); let tag = ''; diff --git a/build/azure-pipelines/publish-types/check-version.ts b/build/azure-pipelines/publish-types/check-version.ts index 137e535353a..35c5a511593 100644 --- a/build/azure-pipelines/publish-types/check-version.ts +++ b/build/azure-pipelines/publish-types/check-version.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as cp from 'child_process'; let tag = ''; diff --git a/build/azure-pipelines/publish-types/update-types.js b/build/azure-pipelines/publish-types/update-types.js index e22911b8470..facece095c3 100644 --- a/build/azure-pipelines/publish-types/update-types.js +++ b/build/azure-pipelines/publish-types/update-types.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const cp = require("child_process"); diff --git a/build/azure-pipelines/publish-types/update-types.ts b/build/azure-pipelines/publish-types/update-types.ts index 7ccd976dba5..a727647e64a 100644 --- a/build/azure-pipelines/publish-types/update-types.ts +++ b/build/azure-pipelines/publish-types/update-types.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import * as cp from 'child_process'; import * as path from 'path'; diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 1bd3ed6f5fd..5a410547456 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const es = require("event-stream"); const Vinyl = require("vinyl"); diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 9ac656e7c26..ddb03c1e839 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; diff --git a/build/azure-pipelines/upload-configuration.js b/build/azure-pipelines/upload-configuration.js index be1bfd018fd..7ee4c8370ce 100644 --- a/build/azure-pipelines/upload-configuration.js +++ b/build/azure-pipelines/upload-configuration.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSettingsSearchBuildId = exports.shouldSetupSettingsSearch = void 0; const path = require("path"); diff --git a/build/azure-pipelines/upload-configuration.ts b/build/azure-pipelines/upload-configuration.ts index 1e718cadfd0..1455cfca78f 100644 --- a/build/azure-pipelines/upload-configuration.ts +++ b/build/azure-pipelines/upload-configuration.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js index 9e6776cd477..c92cd277d85 100644 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ b/build/azure-pipelines/upload-nlsmetadata.js @@ -1,14 +1,16 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const es = require("event-stream"); const vfs = require("vinyl-fs"); const merge = require("gulp-merge-json"); const gzip = require("gulp-gzip"); const identity_1 = require("@azure/identity"); +const path = require("path"); +const fs_1 = require("fs"); const azure = require('gulp-azure-storage'); const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); @@ -18,6 +20,7 @@ function main() { .pipe(merge({ fileName: 'combined.nls.metadata.json', jsonSpace: '', + concatArrays: true, edit: (parsedJson, file) => { if (file.base === 'out-vscode-web-min') { return { vscode: parsedJson }; @@ -62,7 +65,11 @@ function main() { break; } } - const key = 'vscode.' + file.relative.split('/')[0]; + // Get extension id and use that as the key + const folderPath = path.join(file.base, file.relative.split('/')[0]); + const manifest = (0, fs_1.readFileSync)(path.join(folderPath, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const key = manifestJson.publisher + '.' + manifestJson.name; return { [key]: parsedJson }; }, })) diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 0720a817dd3..4749e1f9605 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; import * as merge from 'gulp-merge-json'; import * as gzip from 'gulp-gzip'; import { ClientSecretCredential } from '@azure/identity'; +import path = require('path'); +import { readFileSync } from 'fs'; const azure = require('gulp-azure-storage'); const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; @@ -33,6 +33,7 @@ function main(): Promise { .pipe(merge({ fileName: 'combined.nls.metadata.json', jsonSpace: '', + concatArrays: true, edit: (parsedJson, file) => { if (file.base === 'out-vscode-web-min') { return { vscode: parsedJson }; @@ -81,7 +82,12 @@ function main(): Promise { break; } } - const key = 'vscode.' + file.relative.split('/')[0]; + + // Get extension id and use that as the key + const folderPath = path.join(file.base, file.relative.split('/')[0]); + const manifest = readFileSync(path.join(folderPath, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const key = manifestJson.publisher + '.' + manifestJson.name; return { [key]: parsedJson }; }, })) diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index 22e3e28290d..b852cb958eb 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const path = require("path"); const es = require("event-stream"); diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index e78e783c064..1f76c4c73f4 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as path from 'path'; import * as es from 'event-stream'; import * as Vinyl from 'vinyl'; diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 376f14c6bc6..61e409d4859 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -49,6 +49,7 @@ steps: - script: | mkdir -p .build node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags - task: Cache@2 @@ -58,6 +59,12 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - script: | set -e tar -xzf .build/node_modules_cache/cache.tgz @@ -89,6 +96,13 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | + set -e + node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 4ece0902885..23c2d43a1cc 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -1,4 +1,6 @@ parameters: + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -15,35 +17,58 @@ steps: displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --build --tfs "Unit Tests" } - displayName: Run unit tests (Electron) - timeoutInMinutes: 15 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-node --build } - displayName: Run unit tests (node.js) - timeoutInMinutes: 15 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-node } + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 - - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser, Chromium & Firefox) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn electron $(VSCODE_ARCH) } + exec { .\scripts\test.bat --build --tfs "Unit Tests" } + displayName: Run unit tests (Electron) + timeoutInMinutes: 15 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-node --build } + displayName: Run unit tests (node.js) + timeoutInMinutes: 15 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" } + displayName: Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" exec { yarn gulp ` + compile-extension:configuration-editing ` compile-extension:css-language-features-server ` compile-extension:emmet ` compile-extension:git ` @@ -51,50 +76,70 @@ steps: compile-extension:html-language-features-server ` compile-extension:ipynb ` compile-extension:json-language-features-server ` + compile-extension:markdown-language-features-server ` compile-extension:markdown-language-features ` compile-extension-media ` compile-extension:microsoft-authentication ` compile-extension:typescript-language-features ` compile-extension:vscode-api-tests ` compile-extension:vscode-colorize-tests ` - compile-extension:vscode-custom-editor-tests ` compile-extension:vscode-notebook-tests ` compile-extension:vscode-test-resolver ` } displayName: Build integration tests - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: Run integration tests (Electron) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-integration.bat --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } - displayName: Run integration tests (Browser, Firefox) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } - displayName: Run integration tests (Remote) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { .\scripts\test-remote-integration.bat } + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: Run integration tests (Electron) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox } + displayName: Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat } + displayName: Run integration tests (Remote) + timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - powershell: | @@ -104,36 +149,47 @@ steps: continueOnError: true condition: succeededOrFailed() - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --tracing --headless } - displayName: Run smoke tests (Browser, Chromium) - timeoutInMinutes: 20 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn --cwd test/smoke compile } + displayName: Compile smoke tests - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } - displayName: Run smoke tests (Electron) - timeoutInMinutes: 20 + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { yarn smoketest-no-compile --tracing } + displayName: Run smoke tests (Electron) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn gulp compile-extension:vscode-test-resolver } - exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } - displayName: Run smoke tests (Remote) - timeoutInMinutes: 20 + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } + displayName: Run smoke tests (Electron) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --web --tracing --headless } + displayName: Run smoke tests (Browser, Chromium) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" + exec { yarn gulp compile-extension:vscode-test-resolver } + exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } + displayName: Run smoke tests (Remote) + timeoutInMinutes: 20 - - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - powershell: | . build/azure-pipelines/win32/exec.ps1 exec {.\build\azure-pipelines\win32\listprocesses.bat } @@ -155,7 +211,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - task: PublishPipelineArtifact@0 @@ -171,7 +226,6 @@ steps: continueOnError: true condition: failed() - - ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - task: PublishPipelineArtifact@0 inputs: targetPath: .build\logs diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 7feb8af4e98..bb19e4c0068 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -11,6 +11,11 @@ parameters: type: boolean steps: + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + - task: NodeTool@0 inputs: versionSpec: "16.x" @@ -28,17 +33,19 @@ steps: KeyVaultName: vscode SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output - - task: ExtractFiles@1 - displayName: Extract compilation output - inputs: - archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" - cleanDestinationFolder: false + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: ExtractFiles@1 + displayName: Extract compilation output + inputs: + archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" + cleanDestinationFolder: false - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - powershell: | @@ -69,9 +76,11 @@ steps: displayName: Merge distro - powershell: | + if (!(Test-Path ".build")) { New-Item -Path ".build" -ItemType Directory } "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash displayName: Prepare yarn cache flags - task: Cache@2 @@ -81,6 +90,12 @@ steps: cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache + - task: Cache@2 + inputs: + key: '"builtInDeps" | .build/builtindepshash' + path: .build/builtInExtensions + displayName: Restore built-in extensions + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" @@ -111,6 +126,14 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/lib/builtInExtensions.js } + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download missing built-in extensions + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" @@ -127,13 +150,29 @@ steps: exec { node build/azure-pipelines/mixin } displayName: Mix in quality - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" - displayName: Build + - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build\lib\policies } + displayName: Generate Group Policy definitions + + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "transpile-client" "transpile-extensions" } + displayName: Transpile + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" + displayName: Build - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - powershell: | @@ -151,19 +190,21 @@ steps: displayName: Mix in quality condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } - exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)" - displayName: Build Server - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" + exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } + exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)" + displayName: Build Server + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-win32-test.yml parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index f6ebf6f06e3..750366e752d 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const vscode_universal_bundler_1 = require("vscode-universal-bundler"); const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 26542c2774b..5f4b3170532 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import { makeUniversalApp } from 'vscode-universal-bundler'; import { spawn } from '@malept/cross-spawn-promise'; import * as fs from 'fs-extra'; diff --git a/build/darwin/sign.js b/build/darwin/sign.js index 02b0e1f052e..8aabb83c0f9 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const codesign = require("electron-osx-sign"); const path = require("path"); diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 9b0a9f75afc..1de593fbdd4 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as codesign from 'electron-osx-sign'; import * as path from 'path'; import * as util from '../lib/util'; diff --git a/build/filters.js b/build/filters.js index c2a0a52bdb9..c7f08b40a32 100644 --- a/build/filters.js +++ b/build/filters.js @@ -48,7 +48,6 @@ module.exports.unicodeFilter = [ '!extensions/typescript-language-features/test-workspace/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', - '!extensions/vscode-custom-editor-tests/test-workspace/**', '!extensions/**/dist/**', '!extensions/**/out/**', '!extensions/**/snippets/**', @@ -66,10 +65,6 @@ module.exports.indentationFilter = [ '!**/LICENSE.{txt,rtf}', '!LICENSES.chromium.html', '!**/LICENSE', - '!src/vs/nls.js', - '!src/vs/nls.build.js', - '!src/vs/css.js', - '!src/vs/css.build.js', '!src/vs/loader.js', '!src/vs/base/browser/dompurify/*', '!src/vs/base/common/marked/marked.js', @@ -88,7 +83,6 @@ module.exports.indentationFilter = [ '!extensions/markdown-math/notebook-out/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', - '!extensions/vscode-custom-editor-tests/test-workspace/**', '!build/monaco/**', '!build/win32/**', @@ -145,6 +139,7 @@ module.exports.copyrightFilter = [ '!**/*.xml', '!**/*.sh', '!**/*.zsh', + '!**/*.fish', '!**/*.txt', '!**/*.xpm', '!**/*.opts', diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 3d8a85fd7f9..8e7ff5e3af3 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -29,13 +29,20 @@ const editorEntryPoints = [ name: 'vs/editor/editor.main', include: [], exclude: ['vs/css', 'vs/nls'], - prepend: ['out-editor-build/vs/css.js', 'out-editor-build/vs/nls.js'], + prepend: [ + { path: 'out-editor-build/vs/css.js', amdModuleId: 'vs/css' }, + { path: 'out-editor-build/vs/nls.js', amdModuleId: 'vs/nls' } + ], }, { name: 'vs/base/common/worker/simpleWorker', include: ['vs/editor/common/services/editorSimpleWorker'], - prepend: ['vs/loader.js'], - append: ['vs/base/worker/workerMain'], + exclude: ['vs/nls'], + prepend: [ + { path: 'vs/loader.js' }, + { path: 'vs/nls.js', amdModuleId: 'vs/nls' }, + { path: 'vs/base/worker/workerMain.js' } + ], dest: 'vs/base/worker/workerMain.js' } ]; @@ -109,12 +116,6 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => 'inlineEntryPoint:0.ts', 'inlineEntryPoint:1.ts', 'vs/loader.js', - 'vs/nls.ts', - 'vs/nls.build.js', - 'vs/nls.d.ts', - 'vs/css.js', - 'vs/css.build.js', - 'vs/css.d.ts', 'vs/base/worker/workerMain.ts', ], renames: { diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 3f9f19b4b94..04132cd4400 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -53,6 +53,7 @@ const compilations = [ 'json-language-features/client/tsconfig.json', 'json-language-features/server/tsconfig.json', 'markdown-language-features/preview-src/tsconfig.json', + 'markdown-language-features/server/tsconfig.json', 'markdown-language-features/tsconfig.json', 'markdown-math/tsconfig.json', 'merge-conflict/tsconfig.json', @@ -66,7 +67,6 @@ const compilations = [ 'typescript-language-features/tsconfig.json', 'vscode-api-tests/tsconfig.json', 'vscode-colorize-tests/tsconfig.json', - 'vscode-custom-editor-tests/tsconfig.json', 'vscode-notebook-tests/tsconfig.json', 'vscode-test-resolver/tsconfig.json' ]; @@ -100,7 +100,7 @@ const tasks = compilations.map(function (tsconfigFile) { headerOut = relativeDirname.substr(index + 1) + '/out'; } - function createPipeline(build, emitError) { + function createPipeline(build, emitError, transpileOnly) { const nlsDev = require('vscode-nls-dev'); const tsb = require('./lib/tsb'); const sourcemaps = require('gulp-sourcemaps'); @@ -110,7 +110,7 @@ const tasks = compilations.map(function (tsconfigFile) { overrideOptions.inlineSources = Boolean(build); overrideOptions.base = path.dirname(absolutePath); - const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false }, err => reporter(err.toString())); + const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly }, err => reporter(err.toString())); const pipeline = function () { const input = es.through(); @@ -152,6 +152,16 @@ const tasks = compilations.map(function (tsconfigFile) { const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out)); + const transpileTask = task.define(`transpile-extension:${name}`, task.series(cleanTask, () => { + const pipeline = createPipeline(false, true, true); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); + + return input + .pipe(pipeline()) + .pipe(gulp.dest(out)); + })); + const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false, true); const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); @@ -184,12 +194,16 @@ const tasks = compilations.map(function (tsconfigFile) { })); // Tasks + gulp.task(transpileTask); gulp.task(compileTask); gulp.task(watchTask); - return { compileTask, watchTask, compileBuildTask }; + return { transpileTask, compileTask, watchTask, compileBuildTask }; }); +const transpileExtensionsTask = task.define('transpile-extensions', task.parallel(...tasks.map(t => t.transpileTask))); +gulp.task(transpileExtensionsTask); + const compileExtensionsTask = task.define('compile-extensions', task.parallel(...tasks.map(t => t.compileTask))); gulp.task(compileExtensionsTask); exports.compileExtensionsTask = compileExtensionsTask; @@ -222,8 +236,8 @@ exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask; const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); const compileExtensionsBuildTask = task.define('compile-extensions-build', task.series( cleanExtensionsBuildTask, + task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), task.define('bundle-extensions-build', () => ext.packageLocalExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false, product.extensionsGallery?.serviceUrl).pipe(gulp.dest('.build'))), )); gulp.task(compileExtensionsBuildTask); diff --git a/build/gulpfile.js b/build/gulpfile.js index 65dda505fa7..595cace0b24 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -11,7 +11,7 @@ require('events').EventEmitter.defaultMaxListeners = 100; const gulp = require('gulp'); const util = require('./lib/util'); const task = require('./lib/task'); -const { compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = require('./lib/compilation'); +const { transpileTask, compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = require('./lib/compilation'); const { monacoTypecheckTask/* , monacoTypecheckWatchTask */ } = require('./gulpfile.editor'); const { compileExtensionsTask, watchExtensionsTask, compileExtensionMediaTask } = require('./gulpfile.extensions'); @@ -19,6 +19,10 @@ const { compileExtensionsTask, watchExtensionsTask, compileExtensionMediaTask } gulp.task(compileApiProposalNamesTask); gulp.task(watchApiProposalNamesTask); +// Transpile only +const transpileClientTask = task.define('transpile-client', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), transpileTask('src', 'out'))); +gulp.task(transpileClientTask); + // Fast compile for development time const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), compileApiProposalNamesTask, compileTask('src', 'out', false))); gulp.task(compileClientTask); diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 2abc3f30c43..a07c55232de 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -76,12 +76,14 @@ const serverResources = [ 'out-build/vs/base/node/ps.sh', // Terminal shell integration + 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.fish', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh', + 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.fish', '!**/test/**' ]; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3315caf4bff..25efcfc2cd2 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -6,6 +6,7 @@ 'use strict'; const gulp = require('gulp'); +const merge = require('gulp-merge-json'); const fs = require('fs'); const os = require('os'); const cp = require('child_process'); @@ -68,6 +69,7 @@ const vscodeResources = [ 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + 'out-build/vs/workbench/contrib/terminal/browser/media/*.fish', 'out-build/vs/workbench/contrib/terminal/browser/media/*.ps1', 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', @@ -76,7 +78,7 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/tasks/**/*.json', 'out-build/vs/platform/files/**/*.exe', 'out-build/vs/platform/files/**/*.md', - 'out-build/vs/code/electron-browser/workbench/**', + 'out-build/vs/code/electron-sandbox/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-sandbox/issue/issueReporter.js', 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js', @@ -165,8 +167,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', 'vs/workbench/api/node/extensionHostProcess.js', - 'vs/code/electron-browser/workbench/workbench.html', - 'vs/code/electron-browser/workbench/workbench.js' + 'vs/code/electron-sandbox/workbench/workbench.html', + 'vs/code/electron-sandbox/workbench/workbench.js' ]); const src = gulp.src(out + '/**', { base: '.' }) @@ -331,6 +333,9 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); + result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) + .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); + } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@PRODNAME@@', product.nameLong)) @@ -389,6 +394,8 @@ BUILD_TARGETS.forEach(buildTarget => { } }); +// #region nls + const innoSetupConfig = { 'zh-cn': { codePage: 'CP936', defaultInfo: { name: 'Simplified Chinese', id: '$0804', } }, 'zh-tw': { codePage: 'CP950', defaultInfo: { name: 'Traditional Chinese', id: '$0404' } }, @@ -404,46 +411,23 @@ const innoSetupConfig = { 'tr': { codePage: 'CP1254' } }; -// Transifex Localizations - -const apiHostname = process.env.TRANSIFEX_API_URL; -const apiName = process.env.TRANSIFEX_API_NAME; -const apiToken = process.env.TRANSIFEX_API_TOKEN; - -gulp.task(task.define( - 'vscode-translations-push', - task.series( - compileBuildTask, - compileExtensionsBuildTask, - optimizeVSCodeTask, - function () { - const pathToMetadata = './out-vscode/nls.metadata.json'; - const pathToExtensions = '.build/extensions/*'; - const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; - - return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), - gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), - gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) - ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken) - ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); - } - ) -)); - gulp.task(task.define( 'vscode-translations-export', task.series( - compileBuildTask, + core, compileExtensionsBuildTask, - optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; + const pathToRehWebMetadata = './out-vscode-reh-web/nls.metadata.json'; const pathToExtensions = '.build/extensions/*'; const pathToSetup = 'build/win32/i18n/messages.en.isl'; return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src([pathToMetadata, pathToRehWebMetadata]).pipe(merge({ + fileName: 'nls.metadata.json', + jsonSpace: '', + concatArrays: true + })).pipe(i18n.createXlfFilesForCoreBundle()), gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) ).pipe(vfs.dest('../vscode-translations-export')); @@ -451,13 +435,6 @@ gulp.task(task.define( ) )); -gulp.task('vscode-translations-pull', function () { - return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { - const includeDefault = !!innoSetupConfig[language.id].defaultInfo; - return i18n.pullSetupXlfFiles(apiHostname, apiName, apiToken, language, includeDefault).pipe(vfs.dest(`../vscode-translations-import/${language.id}/setup`)); - })); -}); - gulp.task('vscode-translations-import', function () { const options = minimist(process.argv.slice(2), { string: 'location', @@ -472,3 +449,5 @@ gulp.task('vscode-translations-import', function () { .pipe(vfs.dest(`./build/win32/i18n`)); })); }); + +// #endregion diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 7d0f70f9bef..4a25ca5ab04 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -15,13 +15,18 @@ const util = require('./lib/util'); const task = require('./lib/task'); const packageJson = require('../package.json'); const product = require('../product.json'); -const rpmDependenciesGenerator = require('./linux/rpm/dependencies-generator'); +const dependenciesGenerator = require('./linux/dependencies-generator'); +const sysrootInstaller = require('./linux/debian/install-sysroot'); +const debianRecommendedDependencies = require('./linux/debian/dep-lists').recommendedDeps; const path = require('path'); const root = path.dirname(__dirname); const commit = util.getVersion(root); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); +/** + * @param {string} arch + */ function getDebPackageArch(arch) { return { x64: 'amd64', armhf: 'armhf', arm64: 'arm64' }[arch]; } @@ -74,12 +79,16 @@ function prepareDebPackage(arch) { let size = 0; const control = code.pipe(es.through( function (f) { size += f.isDirectory() ? 4096 : f.contents.length; }, - function () { + async function () { const that = this; + const sysroot = await sysrootInstaller.getSysroot(debArch); + const dependencies = dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch, sysroot); gulp.src('resources/linux/debian/control.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision)) .pipe(replace('@@ARCHITECTURE@@', debArch)) + .pipe(replace('@@DEPENDS@@', dependencies.join(', '))) + .pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', '))) .pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024))) .pipe(rename('DEBIAN/control')) .pipe(es.through(function (f) { that.emit('data', f); }, function () { that.emit('end'); })); @@ -176,7 +185,7 @@ function prepareRpmPackage(arch) { const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); - const dependencies = rpmDependenciesGenerator.getDependencies(binaryDir, product.applicationName, rpmArch); + const dependencies = dependenciesGenerator.getDependencies('rpm', binaryDir, product.applicationName, rpmArch); const spec = gulp.src('resources/linux/rpm/code.spec.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@NAME_LONG@@', product.nameLong)) @@ -210,7 +219,7 @@ function buildRpmPackage(arch) { return shell.task([ 'mkdir -p ' + destination, - 'HOME="$(pwd)/' + destination + '" fakeroot rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, + 'HOME="$(pwd)/' + destination + '" rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, 'cp "' + rpmOut + '/$(ls ' + rpmOut + ')" ' + destination + '/' ]); } diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 4c1259c241c..56b08474033 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -229,7 +229,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const compileWebExtensionsBuildTask = task.define('compile-web-extensions-build', task.series( task.define('clean-web-extensions-build', util.rimraf('.build/web/extensions')), task.define('bundle-web-extensions-build', () => extensions.packageLocalExtensionsStream(true).pipe(gulp.dest('.build/web'))), - task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true, product.extensionsGallery?.serviceUrl).pipe(gulp.dest('.build/web'))), + task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true).pipe(gulp.dest('.build/web'))), task.define('bundle-web-extension-media-build', () => extensions.buildExtensionMedia(false, '.build/web/extensions')), )); gulp.task(compileWebExtensionsBuildTask); diff --git a/build/lib/asar.js b/build/lib/asar.js index 926c580eec6..9ee1616fdfa 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAsar = void 0; const path = require("path"); diff --git a/build/lib/asar.ts b/build/lib/asar.ts index 16c95f8ca5d..44a6416bdfb 100644 --- a/build/lib/asar.ts +++ b/build/lib/asar.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as path from 'path'; import * as es from 'event-stream'; const pickle = require('chromium-pickle-js'); diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index f38871c36d7..38c30234b7e 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getBuiltInExtensions = void 0; +exports.getBuiltInExtensions = exports.getExtensionStream = void 0; const fs = require("fs"); const path = require("path"); const os = require("os"); @@ -44,6 +44,21 @@ function isUpToDate(extension) { return false; } } +function getExtensionDownloadStream(extension) { + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); +} +function getExtensionStream(extension) { + // if the extension exists on disk, use those files instead of downloading anew + if (isUpToDate(extension)) { + log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔ī¸Ž')); + return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + } + return getExtensionDownloadStream(extension); +} +exports.getExtensionStream = getExtensionStream; function syncMarketplaceExtension(extension) { const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); @@ -52,8 +67,7 @@ function syncMarketplaceExtension(extension) { return es.readArray([]); } rimraf.sync(getExtensionPath(extension)); - return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) + return getExtensionDownloadStream(extension) .pipe(vfs.dest('.build/builtInExtensions')) .on('end', () => log(source, extension.name, ansiColors.green('✔ī¸Ž'))); } diff --git a/build/lib/builtInExtensions.ts b/build/lib/builtInExtensions.ts index 971847c8756..912e05653ac 100644 --- a/build/lib/builtInExtensions.ts +++ b/build/lib/builtInExtensions.ts @@ -68,10 +68,26 @@ function isUpToDate(extension: IExtensionDefinition): boolean { } } +function getExtensionDownloadStream(extension: IExtensionDefinition) { + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); +} + +export function getExtensionStream(extension: IExtensionDefinition) { + // if the extension exists on disk, use those files instead of downloading anew + if (isUpToDate(extension)) { + log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔ī¸Ž')); + return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + } + + return getExtensionDownloadStream(extension); +} + function syncMarketplaceExtension(extension: IExtensionDefinition): Stream { const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); - if (isUpToDate(extension)) { log(source, `${extension.name}@${extension.version}`, ansiColors.green('✔ī¸Ž')); return es.readArray([]); @@ -79,8 +95,7 @@ function syncMarketplaceExtension(extension: IExtensionDefinition): Stream { rimraf.sync(getExtensionPath(extension)); - return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) + return getExtensionDownloadStream(extension) .pipe(vfs.dest('.build/builtInExtensions')) .on('end', () => log(source, extension.name, ansiColors.green('✔ī¸Ž'))); } diff --git a/build/lib/bundle.js b/build/lib/bundle.js index 8c1967d4c68..497ac4fb67e 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -42,14 +42,20 @@ function bundle(entryPoints, config, callback) { if (!config.paths['vs/css']) { config.paths['vs/css'] = 'out-build/vs/css.build'; } + config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; + config.buildForceInvokeFactory['vs/nls'] = true; + config.buildForceInvokeFactory['vs/css'] = true; loader.config(config); loader(['require'], (localRequire) => { - const resolvePath = (path) => { - const r = localRequire.toUrl(path); - if (!/\.js/.test(r)) { - return r + '.js'; + const resolvePath = (entry) => { + let r = localRequire.toUrl(entry.path); + if (!r.endsWith('.js')) { + r += '.js'; } - return r; + // avoid packaging the build version of plugins: + r = r.replace('vs/nls.build.js', 'vs/nls.js'); + r = r.replace('vs/css.build.js', 'vs/css.js'); + return { path: r, amdModuleId: entry.amdModuleId }; }; for (const moduleId in entryPointsMap) { const entryPoint = entryPointsMap[moduleId]; @@ -298,9 +304,18 @@ function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, if (module.shim) { mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents)); } - else { + else if (module.defineLocation) { mainResult.sources.push(emitNamedModule(c, module.defineLocation, module.path, contents)); } + else { + const moduleCopy = { + id: module.id, + path: module.path, + defineLocation: module.defineLocation, + dependencies: module.dependencies + }; + throw new Error(`Cannot bundle module '${module.id}' for entry point '${entryPoint}' because it has no shim and it lacks a defineLocation: ${JSON.stringify(moduleCopy)}`); + } }); Object.keys(usedPlugins).forEach((pluginName) => { const plugin = usedPlugins[pluginName]; @@ -321,10 +336,13 @@ function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, plugin.writeFile(pluginName, entryPoint, req, write, {}); } }); - const toIFile = (path) => { - const contents = readFileAndRemoveBOM(path); + const toIFile = (entry) => { + let contents = readFileAndRemoveBOM(entry.path); + if (entry.amdModuleId) { + contents = contents.replace(/^define\(/m, `define("${entry.amdModuleId}",`); + } return { - path: path, + path: entry.path, contents: contents }; }; diff --git a/build/lib/bundle.ts b/build/lib/bundle.ts index a1130d4bbbd..c5fdc2da18c 100644 --- a/build/lib/bundle.ts +++ b/build/lib/bundle.ts @@ -15,7 +15,7 @@ interface IPosition { interface IBuildModuleInfo { id: string; path: string; - defineLocation: IPosition; + defineLocation: IPosition | null; dependencies: string[]; shim: string; exports: any; @@ -42,12 +42,17 @@ interface ILoaderPluginReqFunc { toUrl(something: string): string; } +export interface IExtraFile { + path: string; + amdModuleId?: string; +} + export interface IEntryPoint { name: string; include?: string[]; exclude?: string[]; - prepend?: string[]; - append?: string[]; + prepend?: IExtraFile[]; + append?: IExtraFile[]; dest?: string; } @@ -92,6 +97,13 @@ interface IPartialBundleResult { export interface ILoaderConfig { isBuild?: boolean; paths?: { [path: string]: any }; + /* + * Normally, during a build, no module factories are invoked. This can be used + * to forcefully execute a module's factory. + */ + buildForceInvokeFactory: { + [moduleId: string]: boolean; + }; } /** @@ -132,15 +144,21 @@ export function bundle(entryPoints: IEntryPoint[], config: ILoaderConfig, callba if (!config.paths['vs/css']) { config.paths['vs/css'] = 'out-build/vs/css.build'; } + config.buildForceInvokeFactory = config.buildForceInvokeFactory || {}; + config.buildForceInvokeFactory['vs/nls'] = true; + config.buildForceInvokeFactory['vs/css'] = true; loader.config(config); loader(['require'], (localRequire: any) => { - const resolvePath = (path: string) => { - const r = localRequire.toUrl(path); - if (!/\.js/.test(r)) { - return r + '.js'; + const resolvePath = (entry: IExtraFile) => { + let r = localRequire.toUrl(entry.path); + if (!r.endsWith('.js')) { + r += '.js'; } - return r; + // avoid packaging the build version of plugins: + r = r.replace('vs/nls.build.js', 'vs/nls.js'); + r = r.replace('vs/css.build.js', 'vs/css.js'); + return { path: r, amdModuleId: entry.amdModuleId }; }; for (const moduleId in entryPointsMap) { const entryPoint = entryPointsMap[moduleId]; @@ -403,8 +421,8 @@ function emitEntryPoint( deps: IGraph, entryPoint: string, includedModules: string[], - prepend: string[], - append: string[], + prepend: IExtraFile[], + append: IExtraFile[], dest: string | undefined ): IEmitEntryPointResult { if (!dest) { @@ -444,8 +462,16 @@ function emitEntryPoint( if (module.shim) { mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents)); - } else { + } else if (module.defineLocation) { mainResult.sources.push(emitNamedModule(c, module.defineLocation, module.path, contents)); + } else { + const moduleCopy = { + id: module.id, + path: module.path, + defineLocation: module.defineLocation, + dependencies: module.dependencies + }; + throw new Error(`Cannot bundle module '${module.id}' for entry point '${entryPoint}' because it has no shim and it lacks a defineLocation: ${JSON.stringify(moduleCopy)}`); } }); @@ -470,10 +496,13 @@ function emitEntryPoint( } }); - const toIFile = (path: string): IFile => { - const contents = readFileAndRemoveBOM(path); + const toIFile = (entry: IExtraFile): IFile => { + let contents = readFileAndRemoveBOM(entry.path); + if (entry.amdModuleId) { + contents = contents.replace(/^define\(/m, `define("${entry.amdModuleId}",`); + } return { - path: path, + path: entry.path, contents: contents }; }; diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 2c815b1ad9b..443073cb039 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -1,10 +1,10 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = exports.watchTask = exports.compileTask = void 0; +exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = exports.watchTask = exports.compileTask = exports.transpileTask = void 0; const es = require("event-stream"); const fs = require("fs"); const gulp = require("gulp"); @@ -34,7 +34,7 @@ function getTypeScriptCompilerOptions(src) { options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1; return options; } -function createCompile(src, build, emitError) { +function createCompile(src, build, emitError, transpileOnly) { const tsb = require('./tsb'); const sourcemaps = require('gulp-sourcemaps'); const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); @@ -42,7 +42,7 @@ function createCompile(src, build, emitError) { if (!build) { overrideOptions.inlineSourceMap = true; } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); function pipeline(token) { const bom = require('gulp-bom'); const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); @@ -59,7 +59,7 @@ function createCompile(src, build, emitError) { .pipe(noDeclarationsFilter) .pipe(build ? nls.nls() : es.through()) .pipe(noDeclarationsFilter.restore) - .pipe(sourcemaps.write('.', { + .pipe(transpileOnly ? es.through() : sourcemaps.write('.', { addComment: false, includeContent: !!build, sourceRoot: overrideOptions.sourceRoot @@ -73,12 +73,22 @@ function createCompile(src, build, emitError) { }; return pipeline; } +function transpileTask(src, out) { + return function () { + const transpile = createCompile(src, false, true, true); + const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); + return srcPipe + .pipe(transpile()) + .pipe(gulp.dest(out)); + }; +} +exports.transpileTask = transpileTask; function compileTask(src, out, build) { return function () { if (os.totalmem() < 4000000000) { throw new Error('compilation requires 4GB of RAM'); } - const compile = createCompile(src, build, true); + const compile = createCompile(src, build, true, false); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); const generator = new MonacoGenerator(false); if (src === 'src') { @@ -93,7 +103,7 @@ function compileTask(src, out, build) { exports.compileTask = compileTask; function watchTask(out, build) { return function () { - const compile = createCompile('src', build); + const compile = createCompile('src', build, false, false); const src = gulp.src('src/**', { base: 'src' }); const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); const generator = new MonacoGenerator(true); @@ -178,6 +188,15 @@ class MonacoGenerator { } } function generateApiProposalNames() { + let eol; + try { + const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const match = /\r?\n/m.exec(src); + eol = match ? match[0] : os.EOL; + } + catch { + eol = os.EOL; + } const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; const proposalNames = new Set(); const input = es.through(); @@ -204,7 +223,7 @@ function generateApiProposalNames() { '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join(os.EOL); + ].join(eol); this.emit('data', new File({ path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', contents: Buffer.from(contents) diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 0cb12f4ad53..de1947f2ae1 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as fs from 'fs'; import * as gulp from 'gulp'; @@ -39,7 +37,7 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { return options; } -function createCompile(src: string, build: boolean, emitError?: boolean) { +function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean) { const tsb = require('./tsb') as typeof import('./tsb'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); @@ -50,7 +48,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) { overrideOptions.inlineSourceMap = true; } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); function pipeline(token?: util.ICancellationToken) { const bom = require('gulp-bom') as typeof import('gulp-bom'); @@ -70,7 +68,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) { .pipe(noDeclarationsFilter) .pipe(build ? nls.nls() : es.through()) .pipe(noDeclarationsFilter.restore) - .pipe(sourcemaps.write('.', { + .pipe(transpileOnly ? es.through() : sourcemaps.write('.', { addComment: false, includeContent: !!build, sourceRoot: overrideOptions.sourceRoot @@ -86,6 +84,19 @@ function createCompile(src: string, build: boolean, emitError?: boolean) { return pipeline; } +export function transpileTask(src: string, out: string): () => NodeJS.ReadWriteStream { + + return function () { + + const transpile = createCompile(src, false, true, true); + const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); + + return srcPipe + .pipe(transpile()) + .pipe(gulp.dest(out)); + }; +} + export function compileTask(src: string, out: string, build: boolean): () => NodeJS.ReadWriteStream { return function () { @@ -94,7 +105,7 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod throw new Error('compilation requires 4GB of RAM'); } - const compile = createCompile(src, build, true); + const compile = createCompile(src, build, true, false); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); const generator = new MonacoGenerator(false); if (src === 'src') { @@ -111,7 +122,7 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteStream { return function () { - const compile = createCompile('src', build); + const compile = createCompile('src', build, false, false); const src = gulp.src('src/**', { base: 'src' }); const watchSrc = watch('src/**', { base: 'src', readDelay: 200 }); @@ -215,6 +226,16 @@ class MonacoGenerator { } function generateApiProposalNames() { + let eol: string; + + try { + const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8'); + const match = /\r?\n/m.exec(src); + eol = match ? match[0] : os.EOL; + } catch { + eol = os.EOL; + } + const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; const proposalNames = new Set(); @@ -243,7 +264,7 @@ function generateApiProposalNames() { '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', - ].join(os.EOL); + ].join(eol); this.emit('data', new File({ path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts', diff --git a/build/lib/dependencies.js b/build/lib/dependencies.js index cbc6cec9d81..3b5a9e3e098 100644 --- a/build/lib/dependencies.js +++ b/build/lib/dependencies.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.getProductionDependencies = void 0; const path = require("path"); diff --git a/build/lib/dependencies.ts b/build/lib/dependencies.ts index 516da11be67..eb195154924 100644 --- a/build/lib/dependencies.ts +++ b/build/lib/dependencies.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as path from 'path'; import * as cp from 'child_process'; import * as _ from 'underscore'; diff --git a/build/lib/electron.js b/build/lib/electron.js index 362f6c38e69..c0d9db20a98 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.config = void 0; const fs = require("fs"); diff --git a/build/lib/electron.ts b/build/lib/electron.ts index ed91f8c2f71..76ad172e395 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import * as path from 'path'; import * as vfs from 'vinyl-fs'; diff --git a/build/lib/eslint/code-no-look-behind-regex.js b/build/lib/eslint/code-no-look-behind-regex.js index 7c9c624a407..c7cdf44c181 100644 --- a/build/lib/eslint/code-no-look-behind-regex.js +++ b/build/lib/eslint/code-no-look-behind-regex.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); //------------------------------------------------------------------------------ // Rule Definition diff --git a/build/lib/eslint/code-no-look-behind-regex.ts b/build/lib/eslint/code-no-look-behind-regex.ts index ef3959e39c6..800b59b6479 100644 --- a/build/lib/eslint/code-no-look-behind-regex.ts +++ b/build/lib/eslint/code-no-look-behind-regex.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as ESTree from 'estree'; diff --git a/build/lib/eslint/code-no-unused-expressions.js b/build/lib/eslint/code-no-unused-expressions.js index 530464f37b5..5d9710072e6 100644 --- a/build/lib/eslint/code-no-unused-expressions.js +++ b/build/lib/eslint/code-no-unused-expressions.js @@ -1,14 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js -// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642 -/** - * @fileoverview Flag expressions in statement position that do not side effect - * @author Michael Ficarra - */ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); //------------------------------------------------------------------------------ // Rule Definition diff --git a/build/lib/eslint/code-no-unused-expressions.ts b/build/lib/eslint/code-no-unused-expressions.ts index f5f0728836e..450365de2c6 100644 --- a/build/lib/eslint/code-no-unused-expressions.ts +++ b/build/lib/eslint/code-no-unused-expressions.ts @@ -11,8 +11,6 @@ * @author Michael Ficarra */ -'use strict'; - import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as ESTree from 'estree'; diff --git a/build/lib/eslint/vscode-dts-event-naming.js b/build/lib/eslint/vscode-dts-event-naming.js index 1e376cca734..747e224b397 100644 --- a/build/lib/eslint/vscode-dts-event-naming.js +++ b/build/lib/eslint/vscode-dts-event-naming.js @@ -76,7 +76,7 @@ module.exports = new (_a = class ApiEventNaming { if (def.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { return def; } - else if ((def.type === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature || def.type === experimental_utils_1.AST_NODE_TYPES.Property) && def.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { + else if ((def.type === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature || def.type === experimental_utils_1.AST_NODE_TYPES.PropertyDefinition) && def.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { return def.key; } return this.getIdent(def.parent); diff --git a/build/lib/eslint/vscode-dts-event-naming.ts b/build/lib/eslint/vscode-dts-event-naming.ts index 956ba346087..5e767c6e257 100644 --- a/build/lib/eslint/vscode-dts-event-naming.ts +++ b/build/lib/eslint/vscode-dts-event-naming.ts @@ -88,11 +88,10 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule { if (def.type === AST_NODE_TYPES.Identifier) { return def; - } else if ((def.type === AST_NODE_TYPES.TSPropertySignature || def.type === AST_NODE_TYPES.Property) && def.key.type === AST_NODE_TYPES.Identifier) { + } else if ((def.type === AST_NODE_TYPES.TSPropertySignature || def.type === AST_NODE_TYPES.PropertyDefinition) && def.key.type === AST_NODE_TYPES.Identifier) { return def.key; } return this.getIdent(def.parent); } }; - diff --git a/build/lib/eslint/vscode-dts-provider-naming.js b/build/lib/eslint/vscode-dts-provider-naming.js index 0d94a8a9223..44c2ddd5568 100644 --- a/build/lib/eslint/vscode-dts-provider-naming.js +++ b/build/lib/eslint/vscode-dts-provider-naming.js @@ -17,7 +17,7 @@ module.exports = new (_a = class ApiProviderNaming { const allowed = new Set(config.allowed); return { ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node) => { - const interfaceName = node.parent?.parent.id.name; + const interfaceName = (node.parent?.parent).id.name; if (allowed.has(interfaceName)) { // allowed return; diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 09fb5813a0a..a13c5686b7d 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -25,6 +25,7 @@ const buffer = require('gulp-buffer'); const jsoncParser = require("jsonc-parser"); const dependencies_1 = require("./dependencies"); const _ = require("underscore"); +const builtInExtensions_1 = require("./builtInExtensions"); const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); @@ -246,7 +247,6 @@ const excludedExtensions = [ 'ms-vscode.node-debug', 'ms-vscode.node-debug2', 'vscode-notebook-tests', - 'vscode-custom-editor-tests', ]; const marketplaceWebExtensionsExclude = new Set([ 'ms-vscode.node-debug', @@ -313,16 +313,15 @@ function packageLocalExtensionsStream(forWeb) { .pipe(util2.setExecutableBit(['**/*.sh']))); } exports.packageLocalExtensionsStream = packageLocalExtensionsStream; -function packageMarketplaceExtensionsStream(forWeb, galleryServiceUrl) { +function packageMarketplaceExtensionsStream(forWeb) { const marketplaceExtensionsDescriptions = [ ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), ...(forWeb ? webBuiltInExtensions : []) ]; const marketplaceExtensionsStream = minifyExtensionResources(es.merge(...marketplaceExtensionsDescriptions .map(extension => { - const input = (galleryServiceUrl ? fromMarketplace(galleryServiceUrl, extension) : fromGithub(extension)) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - return updateExtensionPackageJSON(input, (data) => { + const src = (0, builtInExtensions_1.getExtensionStream)(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); + return updateExtensionPackageJSON(src, (data) => { delete data.scripts; delete data.dependencies; delete data.devDependencies; @@ -352,12 +351,20 @@ function scanBuiltinExtensions(extensionsRoot, exclude = []) { const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder)); const packageNLSPath = children.filter(child => child === 'package.nls.json')[0]; const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined; + let browserNlsMetadataPath; + if (packageJSON.browser) { + const browserEntrypointFolderPath = path.join(extensionFolder, path.dirname(packageJSON.browser)); + if (fs.existsSync(path.join(extensionsRoot, browserEntrypointFolderPath, 'nls.metadata.json'))) { + browserNlsMetadataPath = path.join(browserEntrypointFolderPath, 'nls.metadata.json'); + } + } const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; scannedExtensions.push({ extensionPath: extensionFolder, packageJSON, packageNLS, + browserNlsMetadataPath, readmePath: readme ? path.join(extensionFolder, readme) : undefined, changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined, }); @@ -400,6 +407,7 @@ const esbuildMediaScripts = [ 'markdown-language-features/esbuild-preview.js', 'markdown-math/esbuild.js', 'notebook-renderers/esbuild.js', + 'ipynb/esbuild.js', 'simple-browser/esbuild-preview.js', ]; async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 271b25ad228..fdd7d9c3c0d 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -25,6 +25,7 @@ import * as jsoncParser from 'jsonc-parser'; import webpack = require('webpack'); import { getProductionDependencies } from './dependencies'; import _ = require('underscore'); +import { getExtensionStream } from './builtInExtensions'; const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); @@ -286,7 +287,6 @@ const excludedExtensions = [ 'ms-vscode.node-debug', 'ms-vscode.node-debug2', 'vscode-notebook-tests', - 'vscode-custom-editor-tests', ]; const marketplaceWebExtensionsExclude = new Set([ @@ -382,7 +382,7 @@ export function packageLocalExtensionsStream(forWeb: boolean): Stream { ); } -export function packageMarketplaceExtensionsStream(forWeb: boolean, galleryServiceUrl?: string): Stream { +export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream { const marketplaceExtensionsDescriptions = [ ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), ...(forWeb ? webBuiltInExtensions : []) @@ -391,9 +391,8 @@ export function packageMarketplaceExtensionsStream(forWeb: boolean, galleryServi es.merge( ...marketplaceExtensionsDescriptions .map(extension => { - const input = (galleryServiceUrl ? fromMarketplace(galleryServiceUrl, extension) : fromGithub(extension)) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - return updateExtensionPackageJSON(input, (data: any) => { + const src = getExtensionStream(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); + return updateExtensionPackageJSON(src, (data: any) => { delete data.scripts; delete data.dependencies; delete data.devDependencies; @@ -413,6 +412,7 @@ export interface IScannedBuiltinExtension { extensionPath: string; packageJSON: any; packageNLS?: any; + browserNlsMetadataPath?: string; readmePath?: string; changelogPath?: string; } @@ -437,6 +437,13 @@ export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[] const children = fs.readdirSync(path.join(extensionsRoot, extensionFolder)); const packageNLSPath = children.filter(child => child === 'package.nls.json')[0]; const packageNLS = packageNLSPath ? JSON.parse(fs.readFileSync(path.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined; + let browserNlsMetadataPath: string | undefined; + if (packageJSON.browser) { + const browserEntrypointFolderPath = path.join(extensionFolder, path.dirname(packageJSON.browser)); + if (fs.existsSync(path.join(extensionsRoot, browserEntrypointFolderPath, 'nls.metadata.json'))) { + browserNlsMetadataPath = path.join(browserEntrypointFolderPath, 'nls.metadata.json'); + } + } const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; @@ -444,6 +451,7 @@ export function scanBuiltinExtensions(extensionsRoot: string, exclude: string[] extensionPath: extensionFolder, packageJSON, packageNLS, + browserNlsMetadataPath, readmePath: readme ? path.join(extensionFolder, readme) : undefined, changelogPath: changelog ? path.join(extensionFolder, changelog) : undefined, }); @@ -487,6 +495,7 @@ const esbuildMediaScripts = [ 'markdown-language-features/esbuild-preview.js', 'markdown-math/esbuild.js', 'notebook-renderers/esbuild.js', + 'ipynb/esbuild.js', 'simple-browser/esbuild-preview.js', ]; diff --git a/build/lib/git.js b/build/lib/git.js index 7e381b76e68..0aa340347ac 100644 --- a/build/lib/git.js +++ b/build/lib/git.js @@ -1,10 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getVersion = void 0; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVersion = void 0; const path = require("path"); const fs = require("fs"); /** diff --git a/build/lib/git.ts b/build/lib/git.ts index 35671291fc8..dbb424f21df 100644 --- a/build/lib/git.ts +++ b/build/lib/git.ts @@ -2,8 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as path from 'path'; import * as fs from 'fs'; diff --git a/build/lib/i18n.js b/build/lib/i18n.js index dc7c12459b7..b97da8b9fbe 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -491,7 +491,7 @@ function processNlsFiles(opts) { }); } exports.processNlsFiles = processNlsFiles; -const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup'; +const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup', serverProject = 'vscode-server'; function getResource(sourceFile) { let resource; if (/^vs\/platform/.test(sourceFile)) { @@ -509,6 +509,9 @@ function getResource(sourceFile) { else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; } + else if (/^vs\/server/.test(sourceFile)) { + return { name: 'vs/server', project: serverProject }; + } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; @@ -599,17 +602,24 @@ function createXlfFilesForExtensions() { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys = []; + const messages = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 82edafd10b7..dd949e618f2 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -279,7 +279,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/sessionSync", + "name": "vs/workbench/contrib/editSessions", "project": "vscode-workbench" }, { @@ -294,6 +294,14 @@ "name": "vs/workbench/contrib/audioCues", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/deprecatedExtensionMigrator", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/bracketPairColorizer2Telemetry", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/offline", "project": "vscode-workbench" @@ -431,7 +439,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/services/sessionSync", + "name": "vs/workbench/services/editSessions", "project": "vscode-workbench" }, { @@ -463,11 +471,11 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/profiles", + "name": "vs/workbench/contrib/userDataProfile", "project": "vscode-profiles" }, { - "name": "vs/workbench/services/profiles", + "name": "vs/workbench/services/userDataProfile", "project": "vscode-profiles" } ] diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index dae24b53679..20c1dc93f99 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -611,7 +611,8 @@ export function processNlsFiles(opts: { fileHeader: string; languages: Language[ const editorProject: string = 'vscode-editor', workbenchProject: string = 'vscode-workbench', extensionsProject: string = 'vscode-extensions', - setupProject: string = 'vscode-setup'; + setupProject: string = 'vscode-setup', + serverProject: string = 'vscode-server'; export function getResource(sourceFile: string): Resource { let resource: string; @@ -626,6 +627,8 @@ export function getResource(sourceFile: string): Resource { return { name: 'vs/base', project: editorProject }; } else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; + } else if (/^vs\/server/.test(sourceFile)) { + return { name: 'vs/server', project: serverProject }; } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; @@ -714,15 +717,22 @@ export function createXlfFilesForExtensions(): ThroughStream { const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json: PackageJsonFormat = JSON.parse(buffer.toString('utf8')); - const keys = Object.keys(json); - const messages = keys.map((key) => { + const keys: Array = []; + const messages: string[] = []; + Object.keys(json).forEach((key) => { const value = json[key]; if (Is.string(value)) { - return value; + keys.push(key); + messages.push(value); } else if (value) { - return value.message; + keys.push({ + key, + comment: value.comment + }); + messages.push(value.message); } else { - return `Unknown message for key: ${key}`; + keys.push(key); + messages.push(`Unknown message for key: ${key}`); } }); getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index c83d6fcf984..cbc1f95b979 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -51,6 +51,7 @@ const CORE_TYPES = [ 'BigInt64Array', 'btoa', 'atob', + 'AbortController', 'AbortSignal', 'MessageChannel', 'MessagePort', @@ -72,6 +73,11 @@ const RULES = [ target: '**/vs/**/test/**', skip: true // -> skip all test files }, + // TODO@bpasero remove me once electron utility process has landed + { + target: '**/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts', + skip: true + }, // Common: vs/base/common/platform.ts { target: '**/vs/base/common/platform.ts', @@ -223,7 +229,7 @@ function checkFile(program, sourceFile, rule) { } if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; } @@ -247,7 +253,7 @@ function checkFile(program, sourceFile, rule) { for (const disallowedDefinition of rule.disallowedDefinitions) { if (definitionFileName.indexOf(disallowedDefinition) >= 0) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; } diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 95810c3b5f4..12b900162aa 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -52,6 +52,7 @@ const CORE_TYPES = [ 'BigInt64Array', 'btoa', 'atob', + 'AbortController', 'AbortSignal', 'MessageChannel', 'MessagePort', @@ -77,6 +78,12 @@ const RULES: IRule[] = [ skip: true // -> skip all test files }, + // TODO@bpasero remove me once electron utility process has landed + { + target: '**/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts', + skip: true + }, + // Common: vs/base/common/platform.ts { target: '**/vs/base/common/platform.ts', @@ -262,7 +269,7 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; @@ -289,7 +296,7 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) if (definitionFileName.indexOf(disallowedDefinition) >= 0) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index cadd0a03715..3da550fb277 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -115,7 +115,7 @@ function getNodeText(sourceFile: ts.SourceFile, node: { pos: number; end: number return sourceFile.getFullText().substring(node.pos, node.end); } -function hasModifier(modifiers: ts.NodeArray | undefined, kind: ts.SyntaxKind): boolean { +function hasModifier(modifiers: ts.NodeArray | undefined, kind: ts.SyntaxKind): boolean { if (modifiers) { for (let i = 0; i < modifiers.length; i++) { const mod = modifiers[i]; diff --git a/build/lib/optimize.js b/build/lib/optimize.js index d5b957d1195..8333afc7184 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0; const es = require("event-stream"); @@ -35,40 +35,58 @@ function loaderConfig() { } exports.loaderConfig = loaderConfig; const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i; -function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { - let sources = [ - `${src}/vs/loader.js` - ]; - if (bundleLoader) { - sources = sources.concat([ - `${src}/vs/css.js`, - `${src}/vs/nls.js` - ]); - } - let isFirst = true; +function loaderPlugin(src, base, amdModuleId) { return (gulp - .src(sources, { base: `${src}` }) + .src(src, { base }) .pipe(es.through(function (data) { - if (isFirst) { - isFirst = false; - this.emit('data', new VinylFile({ - path: 'fake', - base: '.', - contents: Buffer.from(bundledFileHeader) - })); - this.emit('data', data); + if (amdModuleId) { + let contents = data.contents.toString('utf8'); + contents = contents.replace(/^define\(/m, `define("${amdModuleId}",`); + data.contents = Buffer.from(contents); } - else { - this.emit('data', data); + this.emit('data', data); + }))); +} +function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { + let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` }); + if (bundleLoader) { + loaderStream = es.merge(loaderStream, loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css'), loaderPlugin(`${src}/vs/nls.js`, `${src}`, 'vs/nls')); + } + const files = []; + const order = (f) => { + if (f.path.endsWith('loader.js')) { + return 0; } + if (f.path.endsWith('css.js')) { + return 1; + } + if (f.path.endsWith('nls.js')) { + return 2; + } + return 3; + }; + return (loaderStream + .pipe(es.through(function (data) { + files.push(data); }, function () { + files.sort((a, b) => { + return order(a) - order(b); + }); + files.unshift(new VinylFile({ + path: 'fake', + base: '.', + contents: Buffer.from(bundledFileHeader) + })); if (externalLoaderInfo !== undefined) { - this.emit('data', new VinylFile({ + files.push(new VinylFile({ path: 'fake2', base: '.', contents: Buffer.from(`require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});`) })); } + for (const file of files) { + this.emit('data', file); + } this.emit('end'); })) .pipe(concat('vs/loader.js'))); diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index fc2a2f42661..c7b1981a589 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as gulp from 'gulp'; import * as concat from 'gulp-concat'; @@ -41,41 +39,68 @@ export function loaderConfig() { const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i; -function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: any): NodeJS.ReadWriteStream { - let sources = [ - `${src}/vs/loader.js` - ]; - if (bundleLoader) { - sources = sources.concat([ - `${src}/vs/css.js`, - `${src}/vs/nls.js` - ]); - } - - let isFirst = true; +function loaderPlugin(src: string, base: string, amdModuleId: string | undefined): NodeJS.ReadWriteStream { return ( gulp - .src(sources, { base: `${src}` }) - .pipe(es.through(function (data) { - if (isFirst) { - isFirst = false; - this.emit('data', new VinylFile({ - path: 'fake', - base: '.', - contents: Buffer.from(bundledFileHeader) - })); - this.emit('data', data); - } else { - this.emit('data', data); + .src(src, { base }) + .pipe(es.through(function (data: VinylFile) { + if (amdModuleId) { + let contents = data.contents.toString('utf8'); + contents = contents.replace(/^define\(/m, `define("${amdModuleId}",`); + data.contents = Buffer.from(contents); } + this.emit('data', data); + })) + ); +} + +function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: any): NodeJS.ReadWriteStream { + let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` }); + if (bundleLoader) { + loaderStream = es.merge( + loaderStream, + loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css'), + loaderPlugin(`${src}/vs/nls.js`, `${src}`, 'vs/nls'), + ); + } + + const files: VinylFile[] = []; + const order = (f: VinylFile) => { + if (f.path.endsWith('loader.js')) { + return 0; + } + if (f.path.endsWith('css.js')) { + return 1; + } + if (f.path.endsWith('nls.js')) { + return 2; + } + return 3; + }; + + return ( + loaderStream + .pipe(es.through(function (data) { + files.push(data); }, function () { + files.sort((a, b) => { + return order(a) - order(b); + }); + files.unshift(new VinylFile({ + path: 'fake', + base: '.', + contents: Buffer.from(bundledFileHeader) + })); if (externalLoaderInfo !== undefined) { - this.emit('data', new VinylFile({ + files.push(new VinylFile({ path: 'fake2', base: '.', contents: Buffer.from(`require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});`) })); } + for (const file of files) { + this.emit('data', file); + } this.emit('end'); })) .pipe(concat('vs/loader.js')) diff --git a/build/lib/policies.js b/build/lib/policies.js index 3e2a10df350..83ca05abc89 100644 --- a/build/lib/policies.js +++ b/build/lib/policies.js @@ -413,7 +413,7 @@ async function getLatestStableVersion(updateUrl) { const { name: version } = await res.json(); return version; } -async function getNLS(resourceUrlTemplate, languageId, version) { +async function getSpecificNLS(resourceUrlTemplate, languageId, version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -422,9 +422,42 @@ async function getNLS(resourceUrlTemplate, languageId, version) { }; const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key]); const res = await (0, node_fetch_1.default)(url); + if (res.status !== 200) { + throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); + } const { contents: result } = await res.json(); return result; } +function previousVersion(version) { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)$/.exec(version); + return `${major}.${parseInt(minor) - 1}.${patch}`; +} +async function getNLS(resourceUrlTemplate, languageId, version) { + try { + return await getSpecificNLS(resourceUrlTemplate, languageId, version); + } + catch (err) { + if (/\[404\]/.test(err.message)) { + const thePreviousVersion = previousVersion(version); + console.warn(`Language pack ${languageId}@${version} is missing. Downloading previous version ${thePreviousVersion}...`); + try { + return await getSpecificNLS(resourceUrlTemplate, languageId, thePreviousVersion); + } + catch (err) { + if (/\[404\]/.test(err.message)) { + console.warn(`Language pack ${languageId}@${thePreviousVersion} is missing. Downloading previous version...`); + return await getSpecificNLS(resourceUrlTemplate, languageId, previousVersion(thePreviousVersion)); + } + else { + throw err; + } + } + } + else { + throw err; + } + } +} async function parsePolicies() { const parser = new Parser(); parser.setLanguage(typescript); diff --git a/build/lib/policies.ts b/build/lib/policies.ts index 62ea4d561e5..cd3997e4e1d 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies.ts @@ -593,7 +593,7 @@ async function getLatestStableVersion(updateUrl: string) { return version; } -async function getNLS(resourceUrlTemplate: string, languageId: string, version: string) { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: string) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -603,10 +603,43 @@ async function getNLS(resourceUrlTemplate: string, languageId: string, version: const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]); const res = await fetch(url); + + if (res.status !== 200) { + throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); + } + const { contents: result } = await res.json() as { contents: LanguageTranslations }; return result; } +function previousVersion(version: string): string { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)$/.exec(version)!; + return `${major}.${parseInt(minor) - 1}.${patch}`; +} + +async function getNLS(resourceUrlTemplate: string, languageId: string, version: string) { + try { + return await getSpecificNLS(resourceUrlTemplate, languageId, version); + } catch (err) { + if (/\[404\]/.test(err.message)) { + const thePreviousVersion = previousVersion(version); + console.warn(`Language pack ${languageId}@${version} is missing. Downloading previous version ${thePreviousVersion}...`); + try { + return await getSpecificNLS(resourceUrlTemplate, languageId, thePreviousVersion); + } catch (err) { + if (/\[404\]/.test(err.message)) { + console.warn(`Language pack ${languageId}@${thePreviousVersion} is missing. Downloading previous version...`); + return await getSpecificNLS(resourceUrlTemplate, languageId, previousVersion(thePreviousVersion)); + } else { + throw err; + } + } + } else { + throw err; + } + } +} + async function parsePolicies(): Promise { const parser = new Parser(); parser.setLanguage(typescript); diff --git a/build/lib/preLaunch.js b/build/lib/preLaunch.js index ba8ba1c0382..7d316f594df 100644 --- a/build/lib/preLaunch.js +++ b/build/lib/preLaunch.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); // @ts-check const path = require("path"); diff --git a/build/lib/preLaunch.ts b/build/lib/preLaunch.ts index 57441870cc4..3d3f513b591 100644 --- a/build/lib/preLaunch.ts +++ b/build/lib/preLaunch.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - // @ts-check import * as path from 'path'; diff --git a/build/lib/reporter.js b/build/lib/reporter.js index def8f24a065..0c735614324 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.createReporter = void 0; const es = require("event-stream"); diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index f875fce908a..a9c8b53671a 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as _ from 'underscore'; import * as fancyLog from 'fancy-log'; diff --git a/build/lib/snapshotLoader.js b/build/lib/snapshotLoader.js index ee626a0f7f1..ef1ecadbb43 100644 --- a/build/lib/snapshotLoader.js +++ b/build/lib/snapshotLoader.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; var snaps; (function (snaps) { const fs = require('fs'); diff --git a/build/lib/snapshotLoader.ts b/build/lib/snapshotLoader.ts index 40b06d67d05..c3d66dba7e1 100644 --- a/build/lib/snapshotLoader.ts +++ b/build/lib/snapshotLoader.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - namespace snaps { const fs = require('fs'); diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 192a436899f..1121e56ff94 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -102,13 +102,12 @@ function extractEditor(options) { delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); [ - 'vs/css.build.js', - 'vs/css.d.ts', - 'vs/css.js', + 'vs/css.build.ts', + 'vs/css.ts', 'vs/loader.js', - 'vs/nls.build.js', - 'vs/nls.d.ts', - 'vs/nls.js', + 'vs/loader.d.ts', + 'vs/nls.build.ts', + 'vs/nls.ts', 'vs/nls.mock.ts', ].forEach(copyFile); } diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 992d1ab5288..f2181e6f274 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -115,13 +115,12 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); [ - 'vs/css.build.js', - 'vs/css.d.ts', - 'vs/css.js', + 'vs/css.build.ts', + 'vs/css.ts', 'vs/loader.js', - 'vs/nls.build.js', - 'vs/nls.d.ts', - 'vs/nls.js', + 'vs/loader.d.ts', + 'vs/nls.build.ts', + 'vs/nls.ts', 'vs/nls.mock.ts', ].forEach(copyFile); } diff --git a/build/lib/stats.js b/build/lib/stats.js index 6d705a9a16b..869ac2043b3 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.createStatsStream = void 0; const es = require("event-stream"); diff --git a/build/lib/stats.ts b/build/lib/stats.ts index 6e85cf68e29..fe4b22453b5 100644 --- a/build/lib/stats.ts +++ b/build/lib/stats.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; diff --git a/build/lib/task.js b/build/lib/task.js index d08ab8acde8..ba4babafc18 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.define = exports.parallel = exports.series = void 0; const fancyLog = require("fancy-log"); diff --git a/build/lib/task.ts b/build/lib/task.ts index 4e571ec9294..7d2a4dee016 100644 --- a/build/lib/task.ts +++ b/build/lib/task.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 32881066917..ff18694436d 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.shake = exports.toStringShakeLevel = exports.ShakeLevel = void 0; const fs = require("fs"); @@ -89,6 +89,8 @@ function discoverAndReadFiles(ts, options) { const in_queue = Object.create(null); const queue = []; const enqueue = (moduleId) => { + // To make the treeshaker work on windows... + moduleId = moduleId.replace(/\\/g, '/'); if (in_queue[moduleId]) { return; } @@ -226,6 +228,12 @@ function getColor(node) { function setColor(node, color) { node.$$$color = color; } +function markNeededSourceFile(node) { + node.$$$neededSourceFile = true; +} +function isNeededSourceFile(node) { + return Boolean(node.$$$neededSourceFile); +} function nodeOrParentIsBlack(node) { while (node) { const color = getColor(node); @@ -351,6 +359,19 @@ function markNodes(ts, languageService, options) { } }); } + /** + * Return the parent of `node` which is an ImportDeclaration + */ + function findParentImportDeclaration(node) { + let _node = node; + do { + if (ts.isImportDeclaration(_node)) { + return _node; + } + _node = _node.parent; + } while (_node); + return null; + } function enqueue_gray(node) { if (nodeOrParentIsBlack(node) || getColor(node) === 1 /* NodeColor.Gray */) { return; @@ -418,6 +439,8 @@ function markNodes(ts, languageService, options) { console.warn(`Cannot find source file ${filename}`); return; } + // This source file should survive even if it is empty + markNeededSourceFile(sourceFile); enqueue_black(sourceFile); } function enqueueImport(node, importText) { @@ -470,6 +493,10 @@ function markNodes(ts, languageService, options) { const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); if (symbolImportNode) { setColor(symbolImportNode, 2 /* NodeColor.Black */); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { for (let i = 0, len = symbol.declarations.length; i < len; i++) { @@ -661,11 +688,23 @@ function generateResult(ts, languageService, shakeLevel) { } if (getColor(sourceFile) !== 2 /* NodeColor.Black */) { if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable => don't write this file at all! - return; + // none of the elements are reachable + if (isNeededSourceFile(sourceFile)) { + // this source file must be written, even if nothing is used from it + // because there is an import somewhere for it. + // However, TS complains with empty files with the error "x" is not a module, + // so we will export a dummy variable + result = 'export const __dummy = 0;'; + } + else { + // don't write this file at all! + return; + } + } + else { + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); } - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); } else { result = text; @@ -839,3 +878,4 @@ function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTriv return current; } } +//#endregion diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index c13f4609b8c..ef829426d94 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as fs from 'fs'; import * as path from 'path'; import type * as ts from 'typescript'; @@ -144,6 +142,8 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha const queue: string[] = []; const enqueue = (moduleId: string) => { + // To make the treeshaker work on windows... + moduleId = moduleId.replace(/\\/g, '/'); if (in_queue[moduleId]) { return; } @@ -307,6 +307,12 @@ function getColor(node: ts.Node): NodeColor { function setColor(node: ts.Node, color: NodeColor): void { (node).$$$color = color; } +function markNeededSourceFile(node: ts.SourceFile): void { + (node).$$$neededSourceFile = true; +} +function isNeededSourceFile(node: ts.SourceFile): boolean { + return Boolean((node).$$$neededSourceFile); +} function nodeOrParentIsBlack(node: ts.Node): boolean { while (node) { const color = getColor(node); @@ -449,6 +455,20 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language }); } + /** + * Return the parent of `node` which is an ImportDeclaration + */ + function findParentImportDeclaration(node: ts.Declaration): ts.ImportDeclaration | null { + let _node: ts.Node = node; + do { + if (ts.isImportDeclaration(_node)) { + return _node; + } + _node = _node.parent; + } while (_node); + return null; + } + function enqueue_gray(node: ts.Node): void { if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) { return; @@ -531,6 +551,8 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language console.warn(`Cannot find source file ${filename}`); return; } + // This source file should survive even if it is empty + markNeededSourceFile(sourceFile); enqueue_black(sourceFile); } @@ -590,6 +612,10 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); if (symbolImportNode) { setColor(symbolImportNode, NodeColor.Black); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { @@ -802,11 +828,21 @@ function generateResult(ts: typeof import('typescript'), languageService: ts.Lan if (getColor(sourceFile) !== NodeColor.Black) { if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable => don't write this file at all! - return; + // none of the elements are reachable + if (isNeededSourceFile(sourceFile)) { + // this source file must be written, even if nothing is used from it + // because there is an import somewhere for it. + // However, TS complains with empty files with the error "x" is not a module, + // so we will export a dummy variable + result = 'export const __dummy = 0;'; + } else { + // don't write this file at all! + return; + } + } else { + sourceFile.forEachChild(writeMarkedNodes); + result += sourceFile.endOfFileToken.getFullText(sourceFile); } - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); } else { result = text; } diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index 44b9bc862dd..a0b6423bb61 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -15,6 +15,7 @@ const utils_1 = require("./utils"); const fs_1 = require("fs"); const log = require("fancy-log"); const colors = require("ansi-colors"); +const transpiler_1 = require("./transpiler"); class EmptyDuplex extends stream_1.Duplex { _write(_chunk, _encoding, callback) { callback(); } _read() { this.push(null); } @@ -51,25 +52,21 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) } } // FULL COMPILE stream doing transpile, syntax and semantic diagnostics - let _builder; - function createCompileStream(token) { - if (!_builder) { - _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - } + function createCompileStream(builder, token) { return through(function (file) { // give the file to the compiler if (file.isStream()) { this.emit('error', 'no support for streams'); return; } - _builder.file(file); + builder.file(file); }, function () { // start the compilation process - _builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null)); + builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null)); }); } // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream() { + function createTranspileStream(transpiler) { return through(function (file) { // give the file to the compiler if (file.isStream()) { @@ -79,27 +76,29 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) if (!file.contents) { return; } - const out = ts.transpileModule(String(file.contents), { - compilerOptions: { ...cmdLine.options, declaration: false, sourceMap: false } - }); - if (out.diagnostics) { - out.diagnostics.forEach(printDiagnostic); + if (!config.transpileOnlyIncludesDts && file.path.endsWith('.d.ts')) { + return; } - const outFile = new Vinyl({ - path: file.path.replace(/\.ts$/, '.js'), - cwd: file.cwd, - base: file.base, - contents: Buffer.from(out.outputText), + if (!transpiler.onOutfile) { + transpiler.onOutfile = file => this.queue(file); + } + transpiler.transpile(file); + }, function () { + transpiler.join().then(() => { + this.queue(null); + transpiler.onOutfile = undefined; }); - this.push(outFile); - logFn('Transpiled', file.path); }); } - const result = (token) => { - return config.transplileOnly - ? createTranspileStream() - : createCompileStream(token); - }; + let result; + if (config.transpileOnly) { + const transpiler = new transpiler_1.Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + result = (() => createTranspileStream(transpiler)); + } + else { + const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); + result = ((token) => createCompileStream(_builder, token)); + } result.src = (opts) => { let _pos = 0; const _fileNames = cmdLine.fileNames.slice(0); diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 548094bbf03..384a6beeb93 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -13,6 +13,7 @@ import { strings } from './utils'; import { readFileSync, statSync } from 'fs'; import * as log from 'fancy-log'; import colors = require('ansi-colors'); +import { Transpiler } from './transpiler'; export interface IncrementalCompiler { (token?: any): Readable & Writable; @@ -35,7 +36,7 @@ const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4 export function create( projectPath: string, existingOptions: Partial, - config: { verbose?: boolean; transplileOnly?: boolean }, + config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean }, onError: (message: string) => void = _defaultOnError ): IncrementalCompiler { @@ -73,13 +74,7 @@ export function create( } // FULL COMPILE stream doing transpile, syntax and semantic diagnostics - - let _builder!: builder.ITypeScriptBuilder; - function createCompileStream(token?: builder.CancellationToken): Readable & Writable { - - if (!_builder) { - _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - } + function createCompileStream(builder: builder.ITypeScriptBuilder, token?: builder.CancellationToken): Readable & Writable { return through(function (this: through.ThroughStream, file: Vinyl) { // give the file to the compiler @@ -87,11 +82,11 @@ export function create( this.emit('error', 'no support for streams'); return; } - _builder.file(file); + builder.file(file); }, function (this: { queue(a: any): void }) { // start the compilation process - _builder.build( + builder.build( file => this.queue(file), printDiagnostic, token @@ -100,46 +95,43 @@ export function create( } // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream(): Readable & Writable { - - return through(function (this: through.ThroughStream, file: Vinyl) { + function createTranspileStream(transpiler: Transpiler): Readable & Writable { + return through(function (this: through.ThroughStream & { queue(a: any): void }, file: Vinyl) { // give the file to the compiler if (file.isStream()) { this.emit('error', 'no support for streams'); return; } - if (!file.contents) { return; } - - const out = ts.transpileModule(String(file.contents), { - compilerOptions: { ...cmdLine.options, declaration: false, sourceMap: false } - }); - - if (out.diagnostics) { - out.diagnostics.forEach(printDiagnostic); + if (!config.transpileOnlyIncludesDts && file.path.endsWith('.d.ts')) { + return; } - const outFile = new Vinyl({ - path: file.path.replace(/\.ts$/, '.js'), - cwd: file.cwd, - base: file.base, - contents: Buffer.from(out.outputText), + if (!transpiler.onOutfile) { + transpiler.onOutfile = file => this.queue(file); + } + + transpiler.transpile(file); + + }, function (this: { queue(a: any): void }) { + transpiler.join().then(() => { + this.queue(null); + transpiler.onOutfile = undefined; }); - - this.push(outFile); - - logFn('Transpiled', file.path); }); } - const result = (token: builder.CancellationToken) => { - return config.transplileOnly - ? createTranspileStream() - : createCompileStream(token); - }; + let result: IncrementalCompiler; + if (config.transpileOnly) { + const transpiler = new Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + result = (() => createTranspileStream(transpiler)); + } else { + const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); + result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); + } result.src = (opts?: { cwd?: string; base?: string }) => { let _pos = 0; diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js new file mode 100644 index 00000000000..38e6b5c00e4 --- /dev/null +++ b/build/lib/tsb/transpiler.js @@ -0,0 +1,220 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Transpiler = void 0; +const ts = require("typescript"); +const threads = require("node:worker_threads"); +const Vinyl = require("vinyl"); +const node_os_1 = require("node:os"); +function transpile(tsSrc, options) { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (!isAmd && options.compilerOptions?.module === ts.ModuleKind.AMD) { + // enforce NONE module-system for not-amd cases + options = { ...options, ...{ compilerOptions: { ...options.compilerOptions, module: ts.ModuleKind.None } } }; + } + const out = ts.transpileModule(tsSrc, options); + return { + jsSrc: out.outputText, + diag: out.diagnostics ?? [] + }; +} +if (!threads.isMainThread) { + // WORKER + threads.parentPort?.addListener('message', (req) => { + const res = { + jsSrcs: [], + diagnostics: [] + }; + for (const tsSrc of req.tsSrcs) { + const out = transpile(tsSrc, req.options); + res.jsSrcs.push(out.jsSrc); + res.diagnostics.push(out.diag); + } + threads.parentPort.postMessage(res); + }); +} +class TranspileWorker { + constructor(outFileFn) { + this.id = TranspileWorker.pool++; + this._worker = new threads.Worker(__filename); + this._durations = []; + this._worker.addListener('message', (res) => { + if (!this._pending) { + console.error('RECEIVING data WITHOUT request'); + return; + } + const [resolve, reject, files, options, t1] = this._pending; + const outFiles = []; + const diag = []; + for (let i = 0; i < res.jsSrcs.length; i++) { + // inputs and outputs are aligned across the arrays + const file = files[i]; + const jsSrc = res.jsSrcs[i]; + const diag = res.diagnostics[i]; + if (diag.length > 0) { + diag.push(...diag); + continue; + } + let SuffixTypes; + (function (SuffixTypes) { + SuffixTypes[SuffixTypes["Dts"] = 5] = "Dts"; + SuffixTypes[SuffixTypes["Ts"] = 3] = "Ts"; + SuffixTypes[SuffixTypes["Unknown"] = 0] = "Unknown"; + })(SuffixTypes || (SuffixTypes = {})); + const suffixLen = file.path.endsWith('.d.ts') ? 5 /* SuffixTypes.Dts */ + : file.path.endsWith('.ts') ? 3 /* SuffixTypes.Ts */ + : 0 /* SuffixTypes.Unknown */; + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (suffixLen === 5 /* SuffixTypes.Dts */ && _isDefaultEmpty(jsSrc)) { + continue; + } + const outBase = options.compilerOptions?.outDir ?? file.base; + const outPath = outFileFn(file.path); + outFiles.push(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(jsSrc), + })); + } + this._pending = undefined; + this._durations.push(Date.now() - t1); + if (diag.length > 0) { + reject(diag); + } + else { + resolve(outFiles); + } + }); + } + terminate() { + // console.log(`Worker#${this.id} ENDS after ${this._durations.length} jobs (total: ${this._durations.reduce((p, c) => p + c, 0)}, avg: ${this._durations.reduce((p, c) => p + c, 0) / this._durations.length})`); + this._worker.terminate(); + } + get isBusy() { + return this._pending !== undefined; + } + next(files, options) { + if (this._pending !== undefined) { + throw new Error('BUSY'); + } + return new Promise((resolve, reject) => { + this._pending = [resolve, reject, files, options, Date.now()]; + const req = { + options, + tsSrcs: files.map(file => String(file.contents)) + }; + this._worker.postMessage(req); + }); + } +} +TranspileWorker.pool = 1; +class Transpiler { + constructor(logFn, _onError, configFilePath, _cmdLine) { + this._onError = _onError; + this._cmdLine = _cmdLine; + this._workerPool = []; + this._queue = []; + this._allJobs = []; + logFn('Transpile', `will use ${Transpiler.P} transpile worker`); + this._getOutputFileName = (file) => { + try { + // windows: path-sep normalizing + file = ts.normalizePath(file); + if (!_cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + _cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + _cmdLine.fileNames.push(file); + } + const outfile = ts.getOutputFileNames(_cmdLine, file, true)[0]; + if (isDts) { + _cmdLine.fileNames.pop(); + } + return outfile; + } + catch (err) { + console.error(file, _cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } + async join() { + // wait for all penindg jobs + this._consumeQueue(); + await Promise.allSettled(this._allJobs); + this._allJobs.length = 0; + // terminate all worker + this._workerPool.forEach(w => w.terminate()); + this._workerPool.length = 0; + } + transpile(file) { + if (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + const newLen = this._queue.push(file); + if (newLen > Transpiler.P ** 2) { + this._consumeQueue(); + } + } + _consumeQueue() { + if (this._queue.length === 0) { + // no work... + return; + } + // kinda LAZYily create workers + if (this._workerPool.length === 0) { + for (let i = 0; i < Transpiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + } + } + const freeWorker = this._workerPool.filter(w => !w.isBusy); + if (freeWorker.length === 0) { + // OK, they will pick up work themselves + return; + } + for (const worker of freeWorker) { + if (this._queue.length === 0) { + break; + } + const job = new Promise(resolve => { + const consume = () => { + const files = this._queue.splice(0, Transpiler.P); + if (files.length === 0) { + // DONE + resolve(undefined); + return; + } + // work on the NEXT file + // const [inFile, outFn] = req; + worker.next(files, { compilerOptions: this._cmdLine.options }).then(outFiles => { + if (this.onOutfile) { + outFiles.map(this.onOutfile, this); + } + consume(); + }).catch(err => { + this._onError(err); + }); + }; + consume(); + }); + this._allJobs.push(job); + } + } +} +exports.Transpiler = Transpiler; +Transpiler.P = Math.floor((0, node_os_1.cpus)().length * .5); +function _isDefaultEmpty(src) { + return src + .replace('"use strict";', '') + .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') + .trim().length === 0; +} diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts new file mode 100644 index 00000000000..7bd789ef6eb --- /dev/null +++ b/build/lib/tsb/transpiler.ts @@ -0,0 +1,285 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as threads from 'node:worker_threads'; +import * as Vinyl from 'vinyl'; +import { cpus } from 'node:os'; + +interface TranspileReq { + readonly tsSrcs: string[]; + readonly options: ts.TranspileOptions; +} + +interface TranspileRes { + readonly jsSrcs: string[]; + readonly diagnostics: ts.Diagnostic[][]; +} + +function transpile(tsSrc: string, options: ts.TranspileOptions): { jsSrc: string; diag: ts.Diagnostic[] } { + + const isAmd = /\n(import|export)/m.test(tsSrc); + if (!isAmd && options.compilerOptions?.module === ts.ModuleKind.AMD) { + // enforce NONE module-system for not-amd cases + options = { ...options, ...{ compilerOptions: { ...options.compilerOptions, module: ts.ModuleKind.None } } }; + } + const out = ts.transpileModule(tsSrc, options); + return { + jsSrc: out.outputText, + diag: out.diagnostics ?? [] + }; +} + +if (!threads.isMainThread) { + // WORKER + threads.parentPort?.addListener('message', (req: TranspileReq) => { + const res: TranspileRes = { + jsSrcs: [], + diagnostics: [] + }; + for (const tsSrc of req.tsSrcs) { + const out = transpile(tsSrc, req.options); + res.jsSrcs.push(out.jsSrc); + res.diagnostics.push(out.diag); + } + threads.parentPort!.postMessage(res); + }); +} + +class TranspileWorker { + + private static pool = 1; + + readonly id = TranspileWorker.pool++; + + private _worker = new threads.Worker(__filename); + private _pending?: [resolve: Function, reject: Function, file: Vinyl[], options: ts.TranspileOptions, t1: number]; + private _durations: number[] = []; + + constructor(outFileFn: (fileName: string) => string) { + + this._worker.addListener('message', (res: TranspileRes) => { + if (!this._pending) { + console.error('RECEIVING data WITHOUT request'); + return; + } + + const [resolve, reject, files, options, t1] = this._pending; + + const outFiles: Vinyl[] = []; + const diag: ts.Diagnostic[] = []; + + for (let i = 0; i < res.jsSrcs.length; i++) { + // inputs and outputs are aligned across the arrays + const file = files[i]; + const jsSrc = res.jsSrcs[i]; + const diag = res.diagnostics[i]; + + if (diag.length > 0) { + diag.push(...diag); + continue; + } + const enum SuffixTypes { + Dts = 5, + Ts = 3, + Unknown = 0 + } + const suffixLen = file.path.endsWith('.d.ts') ? SuffixTypes.Dts + : file.path.endsWith('.ts') ? SuffixTypes.Ts + : SuffixTypes.Unknown; + + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (suffixLen === SuffixTypes.Dts && _isDefaultEmpty(jsSrc)) { + continue; + } + + const outBase = options.compilerOptions?.outDir ?? file.base; + const outPath = outFileFn(file.path); + + outFiles.push(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(jsSrc), + })); + } + + this._pending = undefined; + this._durations.push(Date.now() - t1); + + if (diag.length > 0) { + reject(diag); + } else { + resolve(outFiles); + } + }); + } + + terminate() { + // console.log(`Worker#${this.id} ENDS after ${this._durations.length} jobs (total: ${this._durations.reduce((p, c) => p + c, 0)}, avg: ${this._durations.reduce((p, c) => p + c, 0) / this._durations.length})`); + this._worker.terminate(); + } + + get isBusy() { + return this._pending !== undefined; + } + + next(files: Vinyl[], options: ts.TranspileOptions) { + if (this._pending !== undefined) { + throw new Error('BUSY'); + } + return new Promise((resolve, reject) => { + this._pending = [resolve, reject, files, options, Date.now()]; + const req: TranspileReq = { + options, + tsSrcs: files.map(file => String(file.contents)) + }; + this._worker.postMessage(req); + }); + } +} + + +export class Transpiler { + + static P = Math.floor(cpus().length * .5); + + private readonly _getOutputFileName: (name: string) => string; + + public onOutfile?: (file: Vinyl) => void; + + private _workerPool: TranspileWorker[] = []; + private _queue: Vinyl[] = []; + private _allJobs: Promise[] = []; + + constructor( + logFn: (topic: string, message: string) => void, + private readonly _onError: (err: any) => void, + configFilePath: string, + private readonly _cmdLine: ts.ParsedCommandLine + ) { + logFn('Transpile', `will use ${Transpiler.P} transpile worker`); + + + // very complicated logic to re-use TS internal functions to know the output path + // given a TS input path and its config + type InternalTsApi = typeof ts & { + normalizePath(path: string): string; + getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; + }; + this._getOutputFileName = (file) => { + try { + + // windows: path-sep normalizing + file = (ts).normalizePath(file); + + if (!_cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + _cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + _cmdLine.fileNames.push(file); + } + const outfile = (ts).getOutputFileNames(_cmdLine, file, true)[0]; + if (isDts) { + _cmdLine.fileNames.pop(); + } + return outfile; + + } catch (err) { + console.error(file, _cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } + + async join() { + // wait for all penindg jobs + this._consumeQueue(); + await Promise.allSettled(this._allJobs); + this._allJobs.length = 0; + + // terminate all worker + this._workerPool.forEach(w => w.terminate()); + this._workerPool.length = 0; + } + + + transpile(file: Vinyl) { + + if (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + + const newLen = this._queue.push(file); + if (newLen > Transpiler.P ** 2) { + this._consumeQueue(); + } + } + + private _consumeQueue(): void { + + if (this._queue.length === 0) { + // no work... + return; + } + + // kinda LAZYily create workers + if (this._workerPool.length === 0) { + for (let i = 0; i < Transpiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + } + } + + const freeWorker = this._workerPool.filter(w => !w.isBusy); + if (freeWorker.length === 0) { + // OK, they will pick up work themselves + return; + } + + for (const worker of freeWorker) { + if (this._queue.length === 0) { + break; + } + + const job = new Promise(resolve => { + + const consume = () => { + const files = this._queue.splice(0, Transpiler.P); + if (files.length === 0) { + // DONE + resolve(undefined); + return; + } + // work on the NEXT file + // const [inFile, outFn] = req; + worker.next(files, { compilerOptions: this._cmdLine.options }).then(outFiles => { + if (this.onOutfile) { + outFiles.map(this.onOutfile, this); + } + consume(); + }).catch(err => { + this._onError(err); + }); + }; + + consume(); + }); + + this._allJobs.push(job); + } + } +} + +function _isDefaultEmpty(src: string): boolean { + return src + .replace('"use strict";', '') + .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') + .trim().length === 0; +} diff --git a/build/lib/util.js b/build/lib/util.js index 0f6cf5875fc..6353637d7c4 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -1,8 +1,8 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildWebNodePaths = exports.createExternalLoaderConfig = exports.acquireWebNodePaths = exports.getElectronVersion = exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.getVersion = exports.ensureDir = exports.rreddir = exports.rimraf = exports.rewriteSourceMappingURL = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.debounce = exports.incremental = void 0; const es = require("event-stream"); @@ -336,6 +336,13 @@ function acquireWebNodePaths() { } nodePaths[key] = entryPoint; } + // @TODO lramos15 can we make this dynamic like the rest of the node paths + // Add these paths as well for 1DS SDK dependencies. + // Not sure why given the 1DS entrypoint then requires these modules + // they are not fetched from the right location and instead are fetched from out/ + nodePaths['@microsoft/dynamicproto-js'] = 'lib/dist/umd/dynamicproto-js.min.js'; + nodePaths['@microsoft/applicationinsights-shims'] = 'dist/umd/applicationinsights-shims.min.js'; + nodePaths['@microsoft/applicationinsights-core-js'] = 'browser/applicationinsights-core-js.min.js'; return nodePaths; } exports.acquireWebNodePaths = acquireWebNodePaths; diff --git a/build/lib/util.ts b/build/lib/util.ts index abbf9adc927..2511f987fbc 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - import * as es from 'event-stream'; import _debounce = require('debounce'); import * as _filter from 'gulp-filter'; @@ -415,6 +413,13 @@ export function acquireWebNodePaths() { nodePaths[key] = entryPoint; } + // @TODO lramos15 can we make this dynamic like the rest of the node paths + // Add these paths as well for 1DS SDK dependencies. + // Not sure why given the 1DS entrypoint then requires these modules + // they are not fetched from the right location and instead are fetched from out/ + nodePaths['@microsoft/dynamicproto-js'] = 'lib/dist/umd/dynamicproto-js.min.js'; + nodePaths['@microsoft/applicationinsights-shims'] = 'dist/umd/applicationinsights-shims.min.js'; + nodePaths['@microsoft/applicationinsights-core-js'] = 'browser/applicationinsights-core-js.min.js'; return nodePaths; } diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js new file mode 100644 index 00000000000..2bff40cf1d2 --- /dev/null +++ b/build/linux/debian/calculate-deps.js @@ -0,0 +1,70 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generatePackageDeps = void 0; +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const os_1 = require("os"); +const path = require("path"); +const manifests = require("../../../cgmanifest.json"); +const dep_lists_1 = require("./dep-lists"); +function generatePackageDeps(files, arch, sysroot) { + const dependencies = files.map(file => calculatePackageDeps(file, arch, sysroot)); + const additionalDepsSet = new Set(dep_lists_1.additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} +exports.generatePackageDeps = generatePackageDeps; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath, arch, sysroot) { + try { + if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + // Get the Chromium dpkg-shlibdeps file. + const chromiumManifest = manifests.registrations.filter(registration => { + return registration.component.type === 'git' && registration.component.git.name === 'chromium'; + }); + const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; + const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; + const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + const dpkgShlibdepsResult = (0, child_process_1.spawnSync)('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + const requires = new Set(depsStr.split(', ').sort()); + return requires; +} diff --git a/build/linux/debian/calculate-deps.ts b/build/linux/debian/calculate-deps.ts new file mode 100644 index 00000000000..9d6c3a9da80 --- /dev/null +++ b/build/linux/debian/calculate-deps.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import { constants, statSync } from 'fs'; +import { tmpdir } from 'os'; +import path = require('path'); +import * as manifests from '../../../cgmanifest.json'; +import { additionalDeps } from './dep-lists'; +import { DebianArchString } from './types'; + +export function generatePackageDeps(files: string[], arch: DebianArchString, sysroot: string): Set[] { + const dependencies: Set[] = files.map(file => calculatePackageDeps(file, arch, sysroot)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath: string, arch: DebianArchString, sysroot: string): Set { + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + + // Get the Chromium dpkg-shlibdeps file. + const chromiumManifest = manifests.registrations.filter(registration => { + return registration.component.type === 'git' && registration.component.git!.name === 'chromium'; + }); + const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; + const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; + const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, + `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, + `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, + `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + + const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + const requires = new Set(depsStr.split(', ').sort()); + return requires; +} diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js new file mode 100644 index 00000000000..b355d50f01f --- /dev/null +++ b/build/linux/debian/dep-lists.js @@ -0,0 +1,144 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps +// Additional dependencies not in the dpkg-shlibdeps output. +exports.additionalDeps = [ + 'ca-certificates', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnss3 (>= 3.26)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'xdg-utils (>= 1.0.2)' // OS integration +]; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends +// Dependencies that we can only recommend +// for now since some of the older distros don't support them. +exports.recommendedDeps = [ + 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. +]; +exports.referenceGeneratedDepsByArch = { + 'amd64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.14)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.2.5)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc-s1 (>= 3.0)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'armhf': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.4)', + 'libc6 (>= 2.9)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc-s1 (>= 3.0)', + 'libgcc-s1 (>= 3.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'arm64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.0.2)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:4.2)', + 'libgcc1 (>= 1:4.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ] +}; diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts new file mode 100644 index 00000000000..80fe3264b09 --- /dev/null +++ b/build/linux/debian/dep-lists.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps +// Additional dependencies not in the dpkg-shlibdeps output. +export const additionalDeps = [ + 'ca-certificates', // Make sure users have SSL certificates. + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnss3 (>= 3.26)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. + 'xdg-utils (>= 1.0.2)' // OS integration +]; + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends +// Dependencies that we can only recommend +// for now since some of the older distros don't support them. +export const recommendedDeps = [ + 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. +]; + +export const referenceGeneratedDepsByArch = { + 'amd64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.14)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.2.5)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc-s1 (>= 3.0)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'armhf': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libc6 (>= 2.4)', + 'libc6 (>= 2.9)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.5.12)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc-s1 (>= 3.0)', + 'libgcc-s1 (>= 3.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ], + 'arm64': [ + 'ca-certificates', + 'libasound2 (>= 1.0.16)', + 'libatk-bridge2.0-0 (>= 2.5.3)', + 'libatk1.0-0 (>= 2.2.0)', + 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.17)', + 'libcairo2 (>= 1.6.0)', + 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', + 'libdbus-1-3 (>= 1.0.2)', + 'libdrm2 (>= 2.4.38)', + 'libexpat1 (>= 2.0.1)', + 'libgbm1 (>= 8.1~0)', + 'libgcc1 (>= 1:3.0)', + 'libgcc1 (>= 1:4.2)', + 'libgcc1 (>= 1:4.5)', + 'libglib2.0-0 (>= 2.16.0)', + 'libglib2.0-0 (>= 2.39.4)', + 'libgtk-3-0 (>= 3.9.10)', + 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libnspr4 (>= 2:4.9-2~)', + 'libnss3 (>= 2:3.22)', + 'libnss3 (>= 3.26)', + 'libpango-1.0-0 (>= 1.14.0)', + 'libsecret-1-0 (>= 0.18)', + 'libstdc++6 (>= 4.1.1)', + 'libstdc++6 (>= 5)', + 'libstdc++6 (>= 5.2)', + 'libstdc++6 (>= 6)', + 'libx11-6', + 'libx11-6 (>= 2:1.4.99.1)', + 'libxcb1 (>= 1.9.2)', + 'libxcomposite1 (>= 1:0.4.4-1)', + 'libxdamage1 (>= 1:1.1)', + 'libxext6', + 'libxfixes3', + 'libxkbcommon0 (>= 0.4.1)', + 'libxkbfile1', + 'libxrandr2', + 'xdg-utils (>= 1.0.2)' + ] +}; diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js new file mode 100644 index 00000000000..2eaec091873 --- /dev/null +++ b/build/linux/debian/install-sysroot.js @@ -0,0 +1,89 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getSysroot = void 0; +const child_process_1 = require("child_process"); +const crypto_1 = require("crypto"); +const os_1 = require("os"); +const fs = require("fs"); +const https = require("https"); +const path = require("path"); +const util = require("../../lib/util"); +// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. +const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; +const URL_PATH = 'sysroots/toolchain'; +function getSha(filename) { + const hash = (0, crypto_1.createHash)('sha1'); + // Read file 1 MB at a time + const fd = fs.openSync(filename, 'r'); + const buffer = Buffer.alloc(1024 * 1024); + let position = 0; + let bytesRead = 0; + while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { + hash.update(buffer); + position += bytesRead; + } + hash.update(buffer.slice(0, bytesRead)); + return hash.digest('hex'); +} +async function getSysroot(arch) { + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootDictLocation = `${(0, os_1.tmpdir)()}/sysroots.json`; + const result = (0, child_process_1.spawnSync)('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); + } + const sysrootInfo = require(sysrootDictLocation); + const sysrootArch = arch === 'armhf' ? 'bullseye_arm' : `bullseye_${arch}`; + const sysrootDict = sysrootInfo[sysrootArch]; + const tarballFilename = sysrootDict['Tarball']; + const tarballSha = sysrootDict['Sha1Sum']; + const sysroot = path.join((0, os_1.tmpdir)(), sysrootDict['SysrootDir']); + const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/'); + const stamp = path.join(sysroot, '.stamp'); + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { + return sysroot; + } + console.log(`Installing Debian ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + const tarball = path.join(sysroot, tarballFilename); + console.log(`Downloading ${url}`); + let downloadSuccess = false; + for (let i = 0; i < 3 && !downloadSuccess; i++) { + fs.writeFileSync(tarball, ''); + await new Promise((c) => { + https.get(url, (res) => { + res.on('data', (chunk) => { + fs.appendFileSync(tarball, chunk); + }); + res.on('end', () => { + downloadSuccess = true; + c(); + }); + }).on('error', (err) => { + console.error('Encountered an error during the download attempt: ' + err.message); + c(); + }); + }); + } + if (!downloadSuccess) { + fs.rmSync(tarball); + throw new Error('Failed to download ' + url); + } + const sha = getSha(tarball); + if (sha !== tarballSha) { + throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); + } + const proc = (0, child_process_1.spawnSync)('tar', ['xf', tarball, '-C', sysroot]); + if (proc.status) { + throw new Error('Tarball extraction failed with code ' + proc.status); + } + fs.rmSync(tarball); + fs.writeFileSync(stamp, url); + return sysroot; +} +exports.getSysroot = getSysroot; diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts new file mode 100644 index 00000000000..ac9de5b8578 --- /dev/null +++ b/build/linux/debian/install-sysroot.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import { createHash } from 'crypto'; +import { tmpdir } from 'os'; +import * as fs from 'fs'; +import * as https from 'https'; +import * as path from 'path'; +import { DebianArchString } from './types'; +import * as util from '../../lib/util'; + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. +const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; +const URL_PATH = 'sysroots/toolchain'; + +function getSha(filename: fs.PathLike): string { + const hash = createHash('sha1'); + // Read file 1 MB at a time + const fd = fs.openSync(filename, 'r'); + const buffer = Buffer.alloc(1024 * 1024); + let position = 0; + let bytesRead = 0; + while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { + hash.update(buffer); + position += bytesRead; + } + hash.update(buffer.slice(0, bytesRead)); + return hash.digest('hex'); +} + +type SysrootDictEntry = { + Sha1Sum: string; + SysrootDir: string; + Tarball: string; +}; + +export async function getSysroot(arch: DebianArchString): Promise { + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootDictLocation = `${tmpdir()}/sysroots.json`; + const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); + } + const sysrootInfo = require(sysrootDictLocation); + const sysrootArch = arch === 'armhf' ? 'bullseye_arm' : `bullseye_${arch}`; + const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch]; + const tarballFilename = sysrootDict['Tarball']; + const tarballSha = sysrootDict['Sha1Sum']; + const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); + const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/'); + const stamp = path.join(sysroot, '.stamp'); + if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) { + return sysroot; + } + + console.log(`Installing Debian ${arch} root image: ${sysroot}`); + fs.rmSync(sysroot, { recursive: true, force: true }); + fs.mkdirSync(sysroot); + const tarball = path.join(sysroot, tarballFilename); + console.log(`Downloading ${url}`); + let downloadSuccess = false; + for (let i = 0; i < 3 && !downloadSuccess; i++) { + fs.writeFileSync(tarball, ''); + await new Promise((c) => { + https.get(url, (res) => { + res.on('data', (chunk) => { + fs.appendFileSync(tarball, chunk); + }); + res.on('end', () => { + downloadSuccess = true; + c(); + }); + }).on('error', (err) => { + console.error('Encountered an error during the download attempt: ' + err.message); + c(); + }); + }); + } + if (!downloadSuccess) { + fs.rmSync(tarball); + throw new Error('Failed to download ' + url); + } + const sha = getSha(tarball); + if (sha !== tarballSha) { + throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); + } + + const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]); + if (proc.status) { + throw new Error('Tarball extraction failed with code ' + proc.status); + } + fs.rmSync(tarball); + fs.writeFileSync(stamp, url); + return sysroot; +} diff --git a/build/linux/debian/types.js b/build/linux/debian/types.js new file mode 100644 index 00000000000..93c92aac9c6 --- /dev/null +++ b/build/linux/debian/types.js @@ -0,0 +1,11 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isDebianArchString = void 0; +function isDebianArchString(s) { + return ['amd64', 'armhf', 'arm64'].includes(s); +} +exports.isDebianArchString = isDebianArchString; diff --git a/extensions/vscode-custom-editor-tests/src/extension.ts b/build/linux/debian/types.ts similarity index 60% rename from extensions/vscode-custom-editor-tests/src/extension.ts rename to build/linux/debian/types.ts index 0a83f97fce2..e97485ef128 100644 --- a/extensions/vscode-custom-editor-tests/src/extension.ts +++ b/build/linux/debian/types.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; -import { AbcTextEditorProvider } from './customTextEditor'; +export type DebianArchString = 'amd64' | 'armhf' | 'arm64'; -export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(new AbcTextEditorProvider(context).register()); +export function isDebianArchString(s: string): s is DebianArchString { + return ['amd64', 'armhf', 'arm64'].includes(s); } diff --git a/build/linux/rpm/dependencies-generator.js b/build/linux/dependencies-generator.js similarity index 57% rename from build/linux/rpm/dependencies-generator.js rename to build/linux/dependencies-generator.js index 1d91eb8de78..af20828097e 100644 --- a/build/linux/rpm/dependencies-generator.js +++ b/build/linux/dependencies-generator.js @@ -6,18 +6,45 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getDependencies = void 0; const child_process_1 = require("child_process"); -const fs_1 = require("fs"); const path = require("path"); -const dep_lists_1 = require("./dep-lists"); +const calculate_deps_1 = require("./debian/calculate-deps"); +const calculate_deps_2 = require("./rpm/calculate-deps"); +const dep_lists_1 = require("./debian/dep-lists"); +const dep_lists_2 = require("./rpm/dep-lists"); +const types_1 = require("./debian/types"); +const types_2 = require("./rpm/types"); // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. // If false, we warn about new dependencies if they show up -// while running the rpm prepare package task for a release. +// while running the prepare package tasks for a release. // If true, we fail the build if there are new dependencies found during that task. // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES = true; -function getDependencies(buildDir, applicationName, arch) { +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +const bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'swiftshader_libEGL.so', + 'swiftshader_libGLESv2.so', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; +function getDependencies(packageType, buildDir, applicationName, arch, sysroot) { + if (packageType === 'deb') { + if (!(0, types_1.isDebianArchString)(arch)) { + throw new Error('Invalid Debian arch string ' + arch); + } + if (!sysroot) { + throw new Error('Missing sysroot parameter'); + } + } + if (packageType === 'rpm' && !(0, types_2.isRpmArchString)(arch)) { + throw new Error('Invalid RPM arch string ' + arch); + } // Get the files for which we want to find dependencies. const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); @@ -33,26 +60,22 @@ function getDependencies(buildDir, applicationName, arch) { files.push(path.join(buildDir, 'chrome-sandbox')); files.push(path.join(buildDir, 'chrome_crashpad_handler')); // Generate the dependencies. - const dependencies = files.map((file) => calculatePackageDeps(file)); - // Add additional dependencies. - const additionalDepsSet = new Set(dep_lists_1.additionalDeps); - dependencies.push(additionalDepsSet); + const dependencies = packageType === 'deb' ? + (0, calculate_deps_1.generatePackageDeps)(files, arch, sysroot) : + (0, calculate_deps_2.generatePackageDeps)(files); // Merge all the dependencies. const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { - return !dep_lists_1.bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); - const referenceGeneratedDeps = dep_lists_1.referenceGeneratedDepsByArch[arch]; + // Exclude bundled dependencies and sort + const sortedDependencies = Array.from(mergedDependencies).filter(dependency => { + return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); + }).sort(); + const referenceGeneratedDeps = packageType === 'deb' ? + dep_lists_1.referenceGeneratedDepsByArch[arch] : + dep_lists_2.referenceGeneratedDepsByArch[arch]; if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed. ' - + 'Printing newer dependencies list that one can use to compare against referenceGeneratedDeps:\n' - + sortedDependencies.join('\n'); + const failMessage = 'The dependencies list has changed.' + + '\nOld:\n' + referenceGeneratedDeps.join('\n') + + '\nNew:\n' + sortedDependencies.join('\n'); if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { throw new Error(failMessage); } @@ -63,25 +86,7 @@ function getDependencies(buildDir, applicationName, arch) { return sortedDependencies; } exports.getDependencies = getDependencies; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. -function calculatePackageDeps(binaryPath) { - try { - if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } - catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - const findRequiresResult = (0, child_process_1.spawnSync)('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps) { const requires = new Set(); for (const depSet of inputDeps) { diff --git a/build/linux/rpm/dependencies-generator.ts b/build/linux/dependencies-generator.ts similarity index 55% rename from build/linux/rpm/dependencies-generator.ts rename to build/linux/dependencies-generator.ts index 95953ecaa24..34573c4ac12 100644 --- a/build/linux/rpm/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -6,21 +6,49 @@ 'use strict'; import { spawnSync } from 'child_process'; -import { constants, statSync } from 'fs'; import path = require('path'); -import { additionalDeps, bundledDeps, referenceGeneratedDepsByArch } from './dep-lists'; -import { ArchString } from './types'; +import { generatePackageDeps as generatePackageDepsDebian } from './debian/calculate-deps'; +import { generatePackageDeps as generatePackageDepsRpm } from './rpm/calculate-deps'; +import { referenceGeneratedDepsByArch as debianGeneratedDeps } from './debian/dep-lists'; +import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-lists'; +import { DebianArchString, isDebianArchString } from './debian/types'; +import { isRpmArchString, RpmArchString } from './rpm/types'; // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. // If false, we warn about new dependencies if they show up -// while running the rpm prepare package task for a release. +// while running the prepare package tasks for a release. // If true, we fail the build if there are new dependencies found during that task. // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; -export function getDependencies(buildDir: string, applicationName: string, arch: ArchString): string[] { +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +const bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'swiftshader_libEGL.so', + 'swiftshader_libGLESv2.so', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; + +export function getDependencies(packageType: 'deb' | 'rpm', buildDir: string, applicationName: string, arch: string, sysroot?: string): string[] { + if (packageType === 'deb') { + if (!isDebianArchString(arch)) { + throw new Error('Invalid Debian arch string ' + arch); + } + if (!sysroot) { + throw new Error('Missing sysroot parameter'); + } + } + if (packageType === 'rpm' && !isRpmArchString(arch)) { + throw new Error('Invalid RPM arch string ' + arch); + } + // Get the files for which we want to find dependencies. const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); @@ -40,30 +68,25 @@ export function getDependencies(buildDir: string, applicationName: string, arch: files.push(path.join(buildDir, 'chrome_crashpad_handler')); // Generate the dependencies. - const dependencies: Set[] = files.map((file) => calculatePackageDeps(file)); - - // Add additional dependencies. - const additionalDepsSet = new Set(additionalDeps); - dependencies.push(additionalDepsSet); + const dependencies = packageType === 'deb' ? + generatePackageDepsDebian(files, arch as DebianArchString, sysroot!) : + generatePackageDepsRpm(files); // Merge all the dependencies. const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies: string[] = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { + // Exclude bundled dependencies and sort + const sortedDependencies: string[] = Array.from(mergedDependencies).filter(dependency => { return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); + }).sort(); - const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; + const referenceGeneratedDeps = packageType === 'deb' ? + debianGeneratedDeps[arch as DebianArchString] : + rpmGeneratedDeps[arch as RpmArchString]; if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed. ' - + 'Printing newer dependencies list that one can use to compare against referenceGeneratedDeps:\n' - + sortedDependencies.join('\n'); + const failMessage = 'The dependencies list has changed.' + + '\nOld:\n' + referenceGeneratedDeps.join('\n') + + '\nNew:\n' + sortedDependencies.join('\n'); if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { throw new Error(failMessage); } else { @@ -74,27 +97,8 @@ export function getDependencies(buildDir: string, applicationName: string, arch: return sortedDependencies; } -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. -function calculatePackageDeps(binaryPath: string): Set { - try { - if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; -} - -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps: Set[]): Set { const requires = new Set(); for (const depSet of inputDeps) { diff --git a/build/linux/libcxx-fetcher.js b/build/linux/libcxx-fetcher.js index 52606aba169..64c7706ff62 100644 --- a/build/linux/libcxx-fetcher.js +++ b/build/linux/libcxx-fetcher.js @@ -1,11 +1,11 @@ +"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. -'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadLibcxxObjects = exports.downloadLibcxxHeaders = void 0; +// Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. const debug = require("debug"); const extract = require("extract-zip"); const fs = require("fs-extra"); diff --git a/build/linux/libcxx-fetcher.ts b/build/linux/libcxx-fetcher.ts index 0e8f5ade255..a9b97a0d9a6 100644 --- a/build/linux/libcxx-fetcher.ts +++ b/build/linux/libcxx-fetcher.ts @@ -5,8 +5,6 @@ // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. -'use strict'; - import * as debug from 'debug'; import * as extract from 'extract-zip'; import * as fs from 'fs-extra'; diff --git a/build/linux/rpm/calculate-deps.js b/build/linux/rpm/calculate-deps.js new file mode 100644 index 00000000000..325fc9694c3 --- /dev/null +++ b/build/linux/rpm/calculate-deps.js @@ -0,0 +1,35 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generatePackageDeps = void 0; +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const dep_lists_1 = require("./dep-lists"); +function generatePackageDeps(files) { + const dependencies = files.map(file => calculatePackageDeps(file)); + const additionalDepsSet = new Set(dep_lists_1.additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} +exports.generatePackageDeps = generatePackageDeps; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. +function calculatePackageDeps(binaryPath) { + try { + if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + const findRequiresResult = (0, child_process_1.spawnSync)('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); + if (findRequiresResult.status !== 0) { + throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); + } + const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); + return requires; +} diff --git a/build/linux/rpm/calculate-deps.ts b/build/linux/rpm/calculate-deps.ts new file mode 100644 index 00000000000..4be2200c018 --- /dev/null +++ b/build/linux/rpm/calculate-deps.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import { constants, statSync } from 'fs'; +import { additionalDeps } from './dep-lists'; + +export function generatePackageDeps(files: string[]): Set[] { + const dependencies: Set[] = files.map(file => calculatePackageDeps(file)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. +function calculatePackageDeps(binaryPath: string): Set { + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + + const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); + if (findRequiresResult.status !== 0) { + throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); + } + + const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); + return requires; +} diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index 0896b1bb6ca..45c413f7934 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -4,7 +4,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.bundledDeps = exports.additionalDeps = void 0; +exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps // Additional dependencies not in the rpm find-requires output. exports.additionalDeps = [ @@ -17,18 +17,6 @@ exports.additionalDeps = [ 'libcurl.so.4()(64bit)', 'xdg-utils' // OS integration ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -exports.bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; exports.referenceGeneratedDepsByArch = { 'x86_64': [ 'ca-certificates', @@ -74,7 +62,6 @@ exports.referenceGeneratedDepsByArch = { 'libgbm.so.1()(64bit)', 'libgcc_s.so.1()(64bit)', 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -157,7 +144,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -247,7 +233,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 8ef34d0320f..d80c86416a6 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -16,19 +16,6 @@ export const additionalDeps = [ 'xdg-utils' // OS integration ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -export const bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; - export const referenceGeneratedDepsByArch = { 'x86_64': [ 'ca-certificates', @@ -74,7 +61,6 @@ export const referenceGeneratedDepsByArch = { 'libgbm.so.1()(64bit)', 'libgcc_s.so.1()(64bit)', 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -157,7 +143,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -247,7 +232,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/linux/rpm/types.js b/build/linux/rpm/types.js index 56d4e6c56ce..9e140325075 100644 --- a/build/linux/rpm/types.js +++ b/build/linux/rpm/types.js @@ -4,3 +4,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.isRpmArchString = void 0; +function isRpmArchString(s) { + return ['x86_64', 'armv7hl', 'aarch64'].includes(s); +} +exports.isRpmArchString = isRpmArchString; diff --git a/build/linux/rpm/types.ts b/build/linux/rpm/types.ts index 84330949d1d..c6a01da1cf5 100644 --- a/build/linux/rpm/types.ts +++ b/build/linux/rpm/types.ts @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export type ArchString = 'x86_64' | 'armv7hl' | 'aarch64'; +export type RpmArchString = 'x86_64' | 'armv7hl' | 'aarch64'; + +export function isRpmArchString(s: string): s is RpmArchString { + return ['x86_64', 'armv7hl', 'aarch64'].includes(s); +} diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 5ca6a6005a4..57b57f58d4a 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -72,7 +72,7 @@ export interface ICommandHandler { #include(vs/editor/common/core/editOperation): ISingleEditOperation #include(vs/editor/common/core/wordHelper): IWordAtPosition #includeAll(vs/editor/common/model): IScrollEvent -#include(vs/editor/common/diff/diffComputer): IChange, ICharChange, ILineChange +#include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange #include(vs/editor/common/core/dimension): IDimension #includeAll(vs/editor/common/editorCommon): IScrollEvent #includeAll(vs/editor/common/textModelEvents): diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 23f57323e45..d77e0099290 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -28,6 +28,7 @@ exports.dirs = [ 'extensions/jake', 'extensions/json-language-features', 'extensions/json-language-features/server', + 'extensions/markdown-language-features/server', 'extensions/markdown-language-features', 'extensions/markdown-math', 'extensions/merge-conflict', @@ -41,7 +42,6 @@ exports.dirs = [ 'extensions/typescript-language-features', 'extensions/vscode-api-tests', 'extensions/vscode-colorize-tests', - 'extensions/vscode-custom-editor-tests', 'extensions/vscode-notebook-tests', 'extensions/vscode-test-resolver', 'remote', diff --git a/build/package.json b/build/package.json index c259ad65063..3b46323c250 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,6 @@ "@typescript-eslint/experimental-utils": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", "@vscode/iconv-lite-umd": "0.7.0", - "applicationinsights": "1.4.2", "byline": "^5.0.0", "colors": "^1.4.0", "commander": "^7.0.0", @@ -52,7 +51,7 @@ "esbuild": "^0.14.2", "extract-zip": "^2.0.1", "fs-extra": "^9.1.0", - "got": "11.8.1", + "got": "11.8.5", "gulp-merge-json": "^2.1.1", "gulp-shell": "^0.8.0", "jsonc-parser": "^2.3.0", diff --git a/build/tsconfig.json b/build/tsconfig.json index de65bb698d3..ac3ce923add 100644 --- a/build/tsconfig.json +++ b/build/tsconfig.json @@ -3,6 +3,7 @@ "target": "es2020", "lib": ["ES2020"], "module": "commonjs", + "alwaysStrict": true, "removeComments": false, "preserveConstEnums": true, "sourceMap": false, diff --git a/build/win32/code.iss b/build/win32/code.iss index e96ca4bde77..192bbb0d2ba 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1494,7 +1494,7 @@ begin Permissions := '/grant:r "*S-1-5-18:(OI)(CI)F" /grant:r "*S-1-5-32-544:(OI)(CI)F" /grant:r "*S-1-5-11:(OI)(CI)RX" /grant:r "*S-1-5-32-545:(OI)(CI)RX"'; #if "user" == InstallTarget - Permissions := Permissions + ' /grant:r "*S-1-3-0:(OI)(CI)F"'; + Permissions := Permissions + Format(' /grant:r "*S-1-3-0:(OI)(CI)F" /grant:r "%s:(OI)(CI)F"', [GetUserNameString()]); #endif Exec(ExpandConstant('{sys}\icacls.exe'), ExpandConstant('"{app}" /inheritancelevel:r ') + Permissions, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); diff --git a/build/yarn.lock b/build/yarn.lock index d65a62ebce3..4ea17c7f5d4 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -847,16 +847,6 @@ anymatch@^3.0.0, anymatch@^3.1.1, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -applicationinsights@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" - integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -909,21 +899,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" - -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1067,17 +1042,17 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" keyv "^4.0.0" lowercase-keys "^2.0.0" - normalize-url "^4.1.0" + normalize-url "^6.0.1" responselike "^2.0.0" call-bind@^1.0.0: @@ -1185,15 +1160,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== - dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1290,14 +1256,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - core-js@^3.6.5: version "3.15.2" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" @@ -1427,18 +1385,6 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -diagnostic-channel-publishers@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" - integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc= - dependencies: - semver "^5.3.0" - dir-compare@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" @@ -1510,13 +1456,6 @@ electron-osx-sign@^0.4.16: minimist "^1.2.0" plist "^3.0.1" -emitter-listener@^1.0.1, emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1974,17 +1913,17 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -got@11.8.1: - version "11.8.1" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d" - integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q== +got@11.8.5: + version "11.8.5" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" + integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== dependencies: "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" + cacheable-request "^7.0.2" decompress-response "^6.0.0" http2-wrapper "^1.0.0-beta.5.2" lowercase-keys "^2.0.0" @@ -2604,6 +2543,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -3006,7 +2950,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver@^5.1.0, semver@^5.3.0: +semver@^5.1.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== @@ -3059,11 +3003,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -3117,11 +3056,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - stoppable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" diff --git a/cglicenses.json b/cglicenses.json index e969eb826e9..3ee0cfae7d3 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -147,5 +147,75 @@ "", "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] + }, + { + "name": "@vscode/win32-app-container-tokens", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] + }, + { + // Reason: Waiting for https://github.com/microsoft/vscode-markdown-languageservice/pull/9 + "name": "vscode-markdown-languageservice", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] + } + { + // Reason: This product includes AVC coding technology. MPEG LA LLC requires this notice. + "name": "H.264/AVC Video Standard", + "fullLicenseText": [ + "This product is licensed under the AVC patent portfolio license for the personal", + "and non-commercial use of a consumer to (i) encode video in compliance with the AVC standard (\"AVC VIDEO\")", + "and/or (ii) decode AVC video that was encoded by a consumer", + "engaged in a personal and non-commercial activity and/or was obtained from a video provider", + "licensed to provide AVC video. No license is granted or shall be implied for any other use.", + "Additional information may be obtained from MPEG LA LLC. See http://www.MPEGLA.COM.", + "", + "For clarification purposes, this notice does not limit or inhibit the use of the product", + "for normal business uses that are personal to that business which do not include", + "(i) redistribution of the product to third parties, or", + "(ii) creation of content with AVC Standard compliant technologies for distribution to third parties." + ] } ] diff --git a/cgmanifest.json b/cgmanifest.json index 59dc01361ce..bb476c6b183 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "59f4a82f7cf9fd0397aa7bf0273bf5b62433c5da" + "commitHash": "16e28102fdf876ce6d136674ba66343ede07441f" } }, "licenseDetail": [ @@ -40,7 +40,20 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "100.0.4896.160" + "version": "102.0.5005.167" + }, + { + "component": { + "type": "git", + "git": { + "name": "ffmpeg", + "repositoryUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", + "commitHash": "5cd95cdf972ad92c38a4ea2d059ac9d6167302ca" + } + }, + "isOnlyProductionDependency": true, + "license": "LGPL-2.1+", + "version": "4.4.git" }, { "component": { @@ -48,11 +61,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "acb71eab779fb56bf70e8a9e0cb2e82a089a87de" + "commitHash": "442e84a358d75152556b5d087e4dd6a51615330d" } }, "isOnlyProductionDependency": true, - "version": "16.13.2" + "version": "16.14.2" }, { "component": { @@ -60,12 +73,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "6839dd92b84057e79ddce06f8291cc56a0a51ff6" + "commitHash": "b05ccd812e3bb3de5b1546a313e298961653e942" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "18.3.2" + "version": "19.0.12" }, { "component": { @@ -539,6 +552,40 @@ "isOnlyProductionDependency": true, "license": "MIT", "version": "0.10.0" + }, + { + "name": "@vscode/win32-app-container-tokens", + "component": { + "type": "git", + "git": { + "name": "vscode-win32-app-container-tokens", + "repositoryUrl": "https://github.com/microsoft/vscode-win32-app-container-tokens", + "commitHash": "5b871f95fd9cb8efa8ee9a80600510d5e5339137" + } + }, + "licenseDetail":[ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] } ], "version": 1 diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index f2ebd926da2..a124a0432e6 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "jsonc-parser": "^2.2.1", - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.1.0" }, "capabilities": { "virtualWorkspaces": true, @@ -52,7 +52,8 @@ ".devcontainer.json" ], "filenamePatterns": [ - "**/User/snippets/*.json" + "**/User/snippets/*.json", + "**/User/profiles/*/snippets/*.json" ] }, { "id": "json", @@ -78,6 +79,10 @@ "fileMatch": "%APP_SETTINGS_HOME%/settings.json", "url": "vscode://schemas/settings/user" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/profiles/*/settings.json", + "url": "vscode://schemas/settings/profile" + }, { "fileMatch": "%MACHINE_SETTINGS_HOME%/settings.json", "url": "vscode://schemas/settings/machine" diff --git a/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json new file mode 100644 index 00000000000..d224f919109 --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json @@ -0,0 +1,189 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "customizations": { + "type": "object", + "properties": { + "codespaces": { + "type": "object", + "description": "Customizations specific to GitHub Codespaces", + "properties": { + "repositories": { + "type": "object", + "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", + "patternProperties": { + "^[a-zA-Z0-9-_.]+[.]*\/[a-zA-Z0-9-_*]+[.]*$": { + "type": "object", + "additionalProperties": true, + "oneOf": [ + { + "properties": { + "permissions": { + "type": "object", + "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", + "additionalProperties": true, + "anyOf": [ + { + "properties": { + "actions": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "checks": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "contents": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "deployments": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "discussions": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "issues": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "packages": { + "type": "string", + "enum": [ + "read" + ] + } + } + }, + { + "properties": { + "pages": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "pull_requests": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "repository_projects": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "statuses": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + }, + { + "properties": { + "workflows": { + "type": "string", + "enum": [ + "write" + ] + } + } + } + ] + } + } + }, + { + "properties": { + "permissions": { + "type": "string", + "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", + "enum": [ + "read-all", + "write-all" + ] + } + } + } + ] + } + } + } + } + } + } + }, + "codespaces": { + "type": "object", + "additionalProperties": true, + "description": "Codespaces-specific configuration.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/codespaces' instead" + } + } +} diff --git a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json new file mode 100644 index 00000000000..e0f2a8aa68f --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "customizations": { + "type": "object", + "properties": { + "vscode": { + "type": "object", + "properties": { + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + } + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again." + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + } + } + } + } + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", + "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + }, + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/extensions' instead" + }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/settings' instead" + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend.", + "deprecated": true, + "deprecationMessage": "Use 'customizations/vscode/devPort' instead" + } + } +} diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index f63aa685cbe..224a57370b4 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -40,8 +40,8 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { provideCompletionItems(document, position, _token) { const location = getLocation(document.getText(), document.offsetAt(position)); if (isCompletingInsidePropertyStringValue(document, location, position)) { - let range = document.getWordRangeAtPosition(position, /\$\{[^\}]*\}/); - if (!range || range.end.isEqual(position) || range.start.isEqual(position)) { + let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) { range = new vscode.Range(position, position); } diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index a4c0e3945d8..ab41c8220c2 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -96,8 +96,8 @@ export class SettingsDocument { return completions; } - let range = this.document.getWordRangeAtPosition(pos, /\$\{[^\}]*\}/); - if (!range || range.end.isEqual(pos) || range.start.isEqual(pos)) { + let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/); + if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) { range = new vscode.Range(pos, pos); } diff --git a/extensions/configuration-editing/src/test/completion.test.ts b/extensions/configuration-editing/src/test/completion.test.ts new file mode 100644 index 00000000000..e5aec5fc46a --- /dev/null +++ b/extensions/configuration-editing/src/test/completion.test.ts @@ -0,0 +1,594 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import { promises as fs } from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import 'mocha'; + + +const testFolder = fs.mkdtemp(path.join(os.tmpdir(), 'conf-editing-')); + +suite('Completions in settings.json', () => { + const testFile = 'settings.json'; + + test('window.title', async () => { + { // inserting after text + const content = [ + '{', + ' "window.title": "custom|"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "custom${activeEditorShort}"', + '}', + ].join('\n'); + const expected = { label: '${activeEditorShort}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // inserting before a variable + const content = [ + '{', + ' "window.title": "|${activeEditorShort}"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${folderPath}${activeEditorShort}"', + '}', + ].join('\n'); + const expected = { label: '${folderPath}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // inserting after a variable + const content = [ + '{', + ' "window.title": "${activeEditorShort}|"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${activeEditorShort}${folderPath}"', + '}', + ].join('\n'); + const expected = { label: '${folderPath}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // replacing an variable + const content = [ + '{', + ' "window.title": "${a|ctiveEditorShort}"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${activeEditorMedium}"', + '}', + ].join('\n'); + const expected = { label: '${activeEditorMedium}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // replacing a partial variable + const content = [ + '{', + ' "window.title": "${a|"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${dirty}"', + '}', + ].join('\n'); + const expected = { label: '${dirty}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // inserting a literal + const content = [ + '{', + ' "window.title": |', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "window.title": "${activeEditorMedium}"', + '}', + ].join('\n'); + const expected = { label: '"${activeEditorMedium}"', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { // no proposals after literal + const content = [ + '{', + ' "window.title": "${activeEditorShort}" |', + '}', + ].join('\n'); + const expected = { label: '${activeEditorMedium}', notAvailable: true }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + + test('files.associations', async () => { + { + const content = [ + '{', + ' "files.associations": {', + ' |', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.associations": {', + ' "*.${1:extension}": "${2:language}"', + ' }', + '}', + ].join('\n'); + const expected = { label: 'Files with Extension', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.associations": {', + ' |', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.associations": {', + ' "/${1:path to file}/*.${2:extension}": "${3:language}"', + ' }', + '}', + ].join('\n'); + const expected = { label: 'Files with Path', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.associations": {', + ' "*.extension": "|bat"', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.associations": {', + ' "*.extension": "json"', + ' }', + '}', + ].join('\n'); + const expected = { label: '"json"', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.associations": {', + ' "*.extension": "bat"|', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.associations": {', + ' "*.extension": "json"', + ' }', + '}', + ].join('\n'); + const expected = { label: '"json"', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.associations": {', + ' "*.extension": "bat" |', + ' }', + '}', + ].join('\n'); + const expected = { label: '"json"', notAvailable: true }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + test('files.exclude', async () => { + { + const content = [ + '{', + ' "files.exclude": {', + ' |', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.exclude": {', + ' "**/*.${1:extension}": true', + ' }', + '}', + ].join('\n'); + const expected = { label: 'Files by Extension', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.exclude": {', + ' "**/*.extension": |true', + ' }', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.exclude": {', + ' "**/*.extension": { "when": "$(basename).${1:extension}" }', + ' }', + '}', + ].join('\n'); + const expected = { label: 'Files with Siblings by Name', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + test('files.defaultLanguage', async () => { + { + const content = [ + '{', + ' "files.defaultLanguage": "json|"', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.defaultLanguage": "jsonc"', + '}', + ].join('\n'); + const expected = { label: '"jsonc"', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "files.defaultLanguage": |', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "files.defaultLanguage": "jsonc"', + '}', + ].join('\n'); + const expected = { label: '"jsonc"', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + test('remote.extensionKind', async () => { + { + const content = [ + '{', + '\t"remote.extensionKind": {', + '\t\t|', + '\t}', + '}', + ].join('\n'); + const expected = { label: 'vscode.npm' }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + test('remote.portsAttributes', async () => { + { + const content = [ + '{', + ' "remote.portsAttributes": {', + ' |', + ' }', + '}', + ].join('\n'); + const expected = { label: '"3000"' }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); +}); + +suite('Completions in extensions.json', () => { + const testFile = 'extensions.json'; + test('change recommendation', async () => { + { + const content = [ + '{', + ' "recommendations": [', + ' "|a.b"', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "recommendations": [', + ' "ms-vscode.js-debug"', + ' ]', + '}', + ].join('\n'); + const expected = { label: 'ms-vscode.js-debug', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + test('add recommendation', async () => { + { + const content = [ + '{', + ' "recommendations": [', + ' |', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "recommendations": [', + ' "ms-vscode.js-debug"', + ' ]', + '}', + ].join('\n'); + const expected = { label: 'ms-vscode.js-debug', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); +}); + +suite('Completions in launch.json', () => { + const testFile = 'launch.json'; + test('variable completions', async () => { + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Run Extension",', + ' "type": "extensionHost",', + ' "preLaunchTask": "${|defaultBuildTask}"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Run Extension",', + ' "type": "extensionHost",', + ' "preLaunchTask": "${cwd}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Run Extension",', + ' "type": "extensionHost",', + ' "preLaunchTask": "|${defaultBuildTask}"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Run Extension",', + ' "type": "extensionHost",', + ' "preLaunchTask": "${cwd}${defaultBuildTask}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Do It",', + ' "program": "${workspace|"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "configurations": [', + ' {', + ' "name": "Do It",', + ' "program": "${cwd}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); +}); + +suite('Completions in tasks.json', () => { + const testFile = 'tasks.json'; + test('variable completions', async () => { + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "tasks": [', + ' {', + ' "type": "shell",', + ' "command": "${|defaultBuildTask}"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "tasks": [', + ' {', + ' "type": "shell",', + ' "command": "${cwd}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + { + const content = [ + '{', + ' "version": "0.2.0",', + ' "tasks": [', + ' {', + ' "type": "shell",', + ' "command": "${defaultBuildTask}|"', + ' }', + ' ]', + '}', + ].join('\n'); + const resultText = [ + '{', + ' "version": "0.2.0",', + ' "tasks": [', + ' {', + ' "type": "shell",', + ' "command": "${defaultBuildTask}${cwd}"', + ' }', + ' ]', + '}', + ].join('\n'); + const expected = { label: '${cwd}', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); +}); + +suite('Completions in keybindings.json', () => { + const testFile = 'keybindings.json'; + test('context key insertion', async () => { + { + const content = [ + '[', + ' {', + ' "key": "ctrl+k ctrl+,",', + ' "command": "editor.jumpToNextFold",', + ' "when": "|"', + ' }', + ']', + ].join('\n'); + const resultText = [ + '[', + ' {', + ' "key": "ctrl+k ctrl+,",', + ' "command": "editor.jumpToNextFold",', + ' "when": "resourcePath"', + ' }', + ']', + ].join('\n'); + const expected = { label: 'resourcePath', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); + + test('context key replace', async () => { + { + const content = [ + '[', + ' {', + ' "key": "ctrl+k ctrl+,",', + ' "command": "editor.jumpToNextFold",', + ' "when": "resou|rcePath"', + ' }', + ']', + ].join('\n'); + const resultText = [ + '[', + ' {', + ' "key": "ctrl+k ctrl+,",', + ' "command": "editor.jumpToNextFold",', + ' "when": "resource"', + ' }', + ']', + ].join('\n'); + const expected = { label: 'resource', resultText }; + await testCompletion(testFile, 'jsonc', content, expected); + } + }); +}); + +interface ItemDescription { + label: string; + resultText?: string; + notAvailable?: boolean; +} + +async function testCompletion(testFileName: string, languageId: string, content: string, expected: ItemDescription) { + + const offset = content.indexOf('|'); + content = content.substring(0, offset) + content.substring(offset + 1); + + const docUri = vscode.Uri.file(path.join(await testFolder, testFileName)); + await fs.writeFile(docUri.fsPath, content); + + const editor = await setTestContent(docUri, languageId, content); + const position = editor.document.positionAt(offset); + + // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion + const actualCompletions = (await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', docUri, position)) as vscode.CompletionList; + + const matches = actualCompletions.items.filter(completion => { + return completion.label === expected.label; + }); + if (expected.notAvailable) { + assert.strictEqual(matches.length, 0, `${expected.label} should not existing is results`); + } else { + assert.strictEqual(matches.length, 1, `${expected.label} should only existing once: Actual: ${actualCompletions.items.map(c => c.label).join(', ')}`); + + if (expected.resultText) { + const match = matches[0]; + if (match.range && match.insertText) { + const range = match.range instanceof vscode.Range ? match.range : match.range.replacing; + const text = typeof match.insertText === 'string' ? match.insertText : match.insertText.value; + + await editor.edit(eb => eb.replace(range, text)); + assert.strictEqual(editor.document.getText(), expected.resultText); + } else { + assert.fail(`Range or insertText missing`); + } + } + } +} + +async function setTestContent(docUri: vscode.Uri, languageId: string, content: string): Promise { + const ext = vscode.extensions.getExtension('vscode.configuration-editing')!; + await ext.activate(); + + const doc = await vscode.workspace.openTextDocument(docUri); + await vscode.languages.setTextDocumentLanguage(doc, languageId); + const editor = await vscode.window.showTextDocument(doc); + + const fullRange = new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length)); + await editor.edit(eb => eb.replace(fullRange, content)); + return editor; + +} diff --git a/extensions/vscode-custom-editor-tests/src/test/index.ts b/extensions/configuration-editing/src/test/index.ts similarity index 67% rename from extensions/vscode-custom-editor-tests/src/test/index.ts rename to extensions/configuration-editing/src/test/index.ts index 4a5238f776c..beec6063928 100644 --- a/extensions/vscode-custom-editor-tests/src/test/index.ts +++ b/extensions/configuration-editing/src/test/index.ts @@ -6,14 +6,24 @@ const path = require('path'); const testRunner = require('../../../../test/integration/electron/testrunner'); -const suite = 'Custom Editor Tests'; - const options: any = { ui: 'tdd', color: true, timeout: 60000 }; +// These integration tests is being run in multiple environments (electron, web, remote) +// so we need to set the suite name based on the environment as the suite name is used +// for the test results file name +let suite = ''; +if (process.env.VSCODE_BROWSER) { + suite = `${process.env.VSCODE_BROWSER} Browser Integration Configuration-Editing Tests`; +} else if (process.env.REMOTE_VSCODE) { + suite = 'Remote Integration Configuration-Editing Tests'; +} else { + suite = 'Integration Configuration-Editing Tests'; +} + if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { options.reporter = 'mocha-multi-reporters'; options.reporterOptions = { diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index f7ac959fc09..e8272098f1e 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -12,7 +12,7 @@ jsonc-parser@^2.2.1: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index d0f49639167..79c46dad831 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/better-cpp-syntax", "repositoryUrl": "https://github.com/jeff-hykin/better-cpp-syntax", - "commitHash": "156fc0eef532928c9dbf22f622d4a8efc7d97a6b" + "commitHash": "924295fc44bde1a00fab60da3a2caca4509adb25" } }, "license": "MIT", - "version": "1.15.13", + "version": "1.15.18", "description": "The original JSON grammars were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, { diff --git a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json index 7f41320ec40..b8a6aa53111 100644 --- a/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json +++ b/extensions/cpp/syntaxes/cpp.embedded.macro.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jeff-hykin/better-cpp-syntax/commit/156fc0eef532928c9dbf22f622d4a8efc7d97a6b", + "version": "https://github.com/jeff-hykin/better-cpp-syntax/commit/924295fc44bde1a00fab60da3a2caca4509adb25", "name": "C++", "scopeName": "source.cpp.embedded.macro", "patterns": [ @@ -2832,7 +2832,7 @@ }, "diagnostic": { "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", - "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", - "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", - "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", - "end": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", "captures": { "1": { - "name": "keyword.control.goto.cpp" - }, - "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "patterns": [ { "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", @@ -6213,7 +6210,44 @@ } ] }, + "3": { + "name": "keyword.control.goto.cpp" + }, "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "6": { "name": "entity.name.label.call.cpp" } } @@ -6948,7 +6982,7 @@ }, "line": { "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", - "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", - "end": "(? { const document = documents.get(textDocument.uri); if (document) { - console.log(JSON.stringify(options)); - const edits = getLanguageService(document).format(document, range ?? getFullRange(document), options as CSSFormatConfiguration); + const edits = getLanguageService(document).format(document, range ?? getFullRange(document), options); if (edits.length > formatterMaxNumberOfEdits) { const newText = TextDocument.applyEdits(document, edits); return [TextEdit.replace(getFullRange(document), newText)]; diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index 9b7854145e5..95d981a70c7 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -73,10 +73,10 @@ vscode-languageserver-types@3.17.2-next.2: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json index 84f084ffdbd..0d2bd03a376 100644 --- a/extensions/dart/cgmanifest.json +++ b/extensions/dart/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dart-lang/dart-syntax-highlight", "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "9d4857e114b7000d94232d83187ad142961c678a" + "commitHash": "45065882c9f699149cb181a5960c05295b4bfbc6" } }, "licenseDetail": [ diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json index 00f374a6ba2..2b8908551d2 100644 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ b/extensions/dart/syntaxes/dart.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/dart-lang/dart-syntax-highlight/commit/9d4857e114b7000d94232d83187ad142961c678a", + "version": "https://github.com/dart-lang/dart-syntax-highlight/commit/45065882c9f699149cb181a5960c05295b4bfbc6", "name": "Dart", "scopeName": "source.dart", "patterns": [ diff --git a/extensions/debug-auto-launch/package.json b/extensions/debug-auto-launch/package.json index 82c9054c456..80ea639b5f3 100644 --- a/extensions/debug-auto-launch/package.json +++ b/extensions/debug-auto-launch/package.json @@ -33,7 +33,7 @@ ] }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/debug-auto-launch/src/extension.ts b/extensions/debug-auto-launch/src/extension.ts index 572c468d843..86de0ac6ea4 100644 --- a/extensions/debug-auto-launch/src/extension.ts +++ b/extensions/debug-auto-launch/src/extension.ts @@ -8,6 +8,12 @@ import { createServer, Server } from 'net'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +const enum State { + Disabled = 'disabled', + OnlyWithFlag = 'onlyWithFlag', + Smart = 'smart', + Always = 'always', +} const localize = nls.loadMessageBundle(); const TEXT_STATUSBAR_LABEL = { [State.Disabled]: localize('status.text.auto.attach.disabled', 'Auto Attach: Disabled'), @@ -62,12 +68,6 @@ const SETTINGS_CAUSE_REFRESH = new Set( ['autoAttachSmartPattern', SETTING_STATE].map(s => `${SETTING_SECTION}.${s}`), ); -const enum State { - Disabled = 'disabled', - OnlyWithFlag = 'onlyWithFlag', - Smart = 'smart', - Always = 'always', -} let currentState: Promise<{ context: vscode.ExtensionContext; state: State | null }>; let statusItem: vscode.StatusBarItem | undefined; // and there is no status bar item diff --git a/extensions/debug-auto-launch/yarn.lock b/extensions/debug-auto-launch/yarn.lock index 22c406bc73f..90475ddc7e0 100644 --- a/extensions/debug-auto-launch/yarn.lock +++ b/extensions/debug-auto-launch/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/debug-server-ready/package.json b/extensions/debug-server-ready/package.json index 29cee88c0c5..3b570124452 100644 --- a/extensions/debug-server-ready/package.json +++ b/extensions/debug-server-ready/package.json @@ -150,7 +150,7 @@ ] }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/debug-server-ready/yarn.lock b/extensions/debug-server-ready/yarn.lock index 22c406bc73f..90475ddc7e0 100644 --- a/extensions/debug-server-ready/yarn.lock +++ b/extensions/debug-server-ready/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 659be1e0fce..2c1bbda1ad7 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -65,6 +65,7 @@ "emmet.showAbbreviationSuggestions": { "type": "boolean", "default": true, + "scope": "resource", "markdownDescription": "%emmetShowAbbreviationSuggestions%" }, "emmet.includeLanguages": { @@ -125,8 +126,14 @@ "emmet.triggerExpansionOnTab": { "type": "boolean", "default": false, + "scope": "language-overridable", "markdownDescription": "%emmetTriggerExpansionOnTab%" }, + "emmet.useInlineCompletions": { + "type": "boolean", + "default": false, + "markdownDescription": "%emmetUseInlineCompletions%" + }, "emmet.preferences": { "type": "object", "default": {}, diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json index 1cac53a6081..4b8661e7d74 100644 --- a/extensions/emmet/package.nls.json +++ b/extensions/emmet/package.nls.json @@ -59,5 +59,6 @@ "emmetPreferencesOutputInlineBreak": "The number of sibling inline elements needed for line breaks to be placed between those elements. If `0`, inline elements are always expanded onto a single line.", "emmetPreferencesOutputReverseAttributes": "If `true`, reverses attribute merging directions when resolving snippets.", "emmetPreferencesOutputSelfClosingStyle": "Style of self-closing tags: html (`
`), xml (`
`) or xhtml (`
`).", - "emmetPreferencesCssColorShort": "If `true`, color values like `#f` will be expanded to `#fff` instead of `#ffffff`." + "emmetPreferencesCssColorShort": "If `true`, color values like `#f` will be expanded to `#fff` instead of `#ffffff`.", + "emmetUseInlineCompletions": "If `true`, Emmet will use inline completions to suggest expansions. To prevent the non-inline completion item provider from showing up as often while this setting is `true`, turn `#editor.quickSuggestions#` to `inline` or `off` for the `other` item." } diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 45a9510acd5..3a930feea2d 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -264,47 +264,47 @@ export async function wrapWithAbbreviation(args: any): Promise { } export function expandEmmetAbbreviation(args: any): Thenable { - if (!validate() || !vscode.window.activeTextEditor) { - return fallbackTab(); + if (!validate()) { + return Promise.resolve(undefined); } + const editor = vscode.window.activeTextEditor!; + + args = args || {}; + if (!args['language']) { + args['language'] = editor.document.languageId; + } else { + const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ?? []; + if (excludedLanguages.includes(args['language'])) { + return fallbackTab(args['language']); + } + } + const languageId: string = args['language']; + /** * Short circuit the parsing. If previous character is space, do not expand. */ - if (vscode.window.activeTextEditor.selections.length === 1 && - vscode.window.activeTextEditor.selection.isEmpty + if (editor.selections.length === 1 && editor.selection.isEmpty ) { - const anchor = vscode.window.activeTextEditor.selection.anchor; + const anchor = editor.selection.anchor; if (anchor.character === 0) { - return fallbackTab(); + return fallbackTab(languageId); } const prevPositionAnchor = anchor.translate(0, -1); - const prevText = vscode.window.activeTextEditor.document.getText(new vscode.Range(prevPositionAnchor, anchor)); + const prevText = editor.document.getText(new vscode.Range(prevPositionAnchor, anchor)); if (prevText === ' ' || prevText === '\t') { - return fallbackTab(); + return fallbackTab(languageId); } } - args = args || {}; - if (!args['language']) { - args['language'] = vscode.window.activeTextEditor.document.languageId; - } else { - const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : []; - if (excludedLanguages.indexOf(vscode.window.activeTextEditor.document.languageId) > -1) { - return fallbackTab(); - } - } const syntax = getSyntaxFromArgs(args); if (!syntax) { - return fallbackTab(); + return fallbackTab(languageId); } - - const editor = vscode.window.activeTextEditor; - // When tabbed on a non empty selection, do not treat it as an emmet abbreviation, and fallback to tab instead - if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) { - return fallbackTab(); + if (vscode.workspace.getConfiguration('emmet', { languageId })['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) { + return fallbackTab(languageId); } const abbreviationList: ExpandAbbreviationInput[] = []; @@ -325,7 +325,7 @@ export function expandEmmetAbbreviation(args: any): Thenable explicitly // else we will end up with <
@@ -415,12 +415,12 @@ export function expandEmmetAbbreviation(args: any): Thenable { - return success ? Promise.resolve(undefined) : fallbackTab(); + return success ? Promise.resolve(undefined) : fallbackTab(languageId); }); } -function fallbackTab(): Thenable { - if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true) { +function fallbackTab(languageId: string): Thenable { + if (vscode.workspace.getConfiguration('emmet', { languageId })['triggerExpansionOnTab'] === true) { return vscode.commands.executeCommand('tab'); } return Promise.resolve(true); @@ -470,13 +470,13 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen && propertyNode.separator && offset >= propertyNode.separatorToken.end && offset <= propertyNode.terminatorToken.start - && abbreviation.indexOf(':') === -1) { + && !abbreviation.includes(':')) { return hexColorRegex.test(abbreviation) || abbreviation === '!'; } if (!propertyNode.terminatorToken && propertyNode.separator && offset >= propertyNode.separatorToken.end - && abbreviation.indexOf(':') === -1) { + && !abbreviation.includes(':')) { return hexColorRegex.test(abbreviation) || abbreviation === '!'; } if (hexColorRegex.test(abbreviation) || abbreviation === '!') { @@ -529,7 +529,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen const typeAttribute = (currentHtmlNode.attributes || []).filter(x => x.name.toString() === 'type')[0]; const typeValue = typeAttribute ? typeAttribute.value.toString() : ''; - if (allowedMimeTypesInScriptTag.indexOf(typeValue) > -1) { + if (allowedMimeTypesInScriptTag.includes(typeValue)) { return true; } diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index c8a6f657d75..96ed024d593 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -31,7 +31,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi if (expandedText.startsWith('<')) { this.lastCompletionType = 'html'; - } else if (expandedText.indexOf(':') > 0 && expandedText.endsWith(';')) { + } else if (expandedText.includes(':') && expandedText.endsWith(';')) { this.lastCompletionType = 'css'; } else { this.lastCompletionType = undefined; @@ -43,7 +43,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi private provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext): Thenable | undefined { const emmetConfig = vscode.workspace.getConfiguration('emmet'); const excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : []; - if (excludedLanguages.indexOf(document.languageId) > -1) { + if (excludedLanguages.includes(document.languageId)) { return; } diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index 53824c4de29..daed62d7c65 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -23,7 +23,7 @@ import { addFileToParseCache, clearParseCache, removeFileFromParseCache } from ' export function activateEmmetExtension(context: vscode.ExtensionContext) { migrateEmmetExtensionsPath(); - registerCompletionProviders(context); + refreshCompletionProviders(context); updateEmmetExtensionsPath(); context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.wrapWithAbbreviation', (args) => { @@ -122,8 +122,8 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) { })); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('emmet.includeLanguages')) { - registerCompletionProviders(context); + if (e.affectsConfiguration('emmet.includeLanguages') || e.affectsConfiguration('emmet.useInlineCompletions')) { + refreshCompletionProviders(context); } if (e.affectsConfiguration('emmet.extensionsPath')) { updateEmmetExtensionsPath(); @@ -158,45 +158,82 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) { * Holds any registered completion providers by their language strings */ const languageMappingForCompletionProviders: Map = new Map(); -const completionProvidersMapping: Map = new Map(); +const completionProviderDisposables: vscode.Disposable[] = []; + +function refreshCompletionProviders(_: vscode.ExtensionContext) { + clearCompletionProviderInfo(); -function registerCompletionProviders(context: vscode.ExtensionContext) { const completionProvider = new DefaultCompletionItemProvider(); - const includedLanguages = getMappingForIncludedLanguages(); + const inlineCompletionProvider: vscode.InlineCompletionItemProvider = { + async provideInlineCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.InlineCompletionContext, token: vscode.CancellationToken) { + const items = await completionProvider.provideCompletionItems(document, position, token, { triggerCharacter: undefined, triggerKind: vscode.CompletionTriggerKind.Invoke }); + if (!items) { + return undefined; + } + const item = items.items[0]; + if (!item) { + return undefined; + } + const range = item.range as vscode.Range; + if (document.getText(range) !== item.label) { + // We only want to show an inline completion if we are really sure the user meant emmet. + // If the user types `d`, we don't want to suggest `
`. + return undefined; + } + + return [ + { + insertText: item.insertText as any, + filterText: item.label as any, + range + } + ]; + } + }; + + const useInlineCompletionProvider = vscode.workspace.getConfiguration('emmet').get('useInlineCompletions'); + const includedLanguages = getMappingForIncludedLanguages(); Object.keys(includedLanguages).forEach(language => { if (languageMappingForCompletionProviders.has(language) && languageMappingForCompletionProviders.get(language) === includedLanguages[language]) { return; } - if (languageMappingForCompletionProviders.has(language)) { - const mapping = completionProvidersMapping.get(language); - if (mapping) { - mapping.dispose(); - } - languageMappingForCompletionProviders.delete(language); - completionProvidersMapping.delete(language); + if (useInlineCompletionProvider) { + const inlineCompletionsProvider = vscode.languages.registerInlineCompletionItemProvider({ language, scheme: '*' }, inlineCompletionProvider); + completionProviderDisposables.push(inlineCompletionsProvider); } - const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]); - context.subscriptions.push(provider); + const explicitProvider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]); + completionProviderDisposables.push(explicitProvider); languageMappingForCompletionProviders.set(language, includedLanguages[language]); - completionProvidersMapping.set(language, provider); }); Object.keys(LANGUAGE_MODES).forEach(language => { if (!languageMappingForCompletionProviders.has(language)) { - const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[language]); - context.subscriptions.push(provider); + if (useInlineCompletionProvider) { + const inlineCompletionsProvider = vscode.languages.registerInlineCompletionItemProvider({ language, scheme: '*' }, inlineCompletionProvider); + completionProviderDisposables.push(inlineCompletionsProvider); + } + + const explicitProvider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[language]); + completionProviderDisposables.push(explicitProvider); languageMappingForCompletionProviders.set(language, language); - completionProvidersMapping.set(language, provider); } }); } +function clearCompletionProviderInfo() { + languageMappingForCompletionProviders.clear(); + let disposable: vscode.Disposable | undefined; + while (disposable = completionProviderDisposables.pop()) { + disposable.dispose(); + } +} + export function deactivate() { - completionProvidersMapping.clear(); + clearCompletionProviderInfo(); clearParseCache(); } diff --git a/extensions/emmet/src/selectItemHTML.ts b/extensions/emmet/src/selectItemHTML.ts index 6400913396c..2465d3da652 100644 --- a/extensions/emmet/src/selectItemHTML.ts +++ b/extensions/emmet/src/selectItemHTML.ts @@ -140,7 +140,7 @@ function getNextAttribute(document: vscode.TextDocument, selectionStart: number, } // Fetch the next word in the attr value - if (attr.value.toString().indexOf(' ') === -1) { + if (!attr.value.toString().includes(' ')) { // attr value does not have space, so no next word to find continue; } diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index c185400bd75..9254efe8f43 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -48,7 +48,7 @@ const invokeCompletionContext: CompletionContext = { suite('Tests for Expand Abbreviations (HTML)', () => { const oldValueForExcludeLanguages = workspace.getConfiguration('emmet').inspect('excludeLanguages'); - const oldValueForInlcudeLanguages = workspace.getConfiguration('emmet').inspect('includeLanguages'); + const oldValueForIncludeLanguages = workspace.getConfiguration('emmet', null).inspect('includeLanguages'); teardown(closeAllEditors); test('Expand snippets (HTML)', () => { @@ -374,7 +374,7 @@ suite('Tests for Expand Abbreviations (HTML)', () => { await expandPromise; assert.strictEqual(editor.document.getText(), htmlContents.replace('span.bye', '')); }); - return workspace.getConfiguration('emmet').update('includeLanguages', oldValueForInlcudeLanguages || {}, ConfigurationTarget.Global); + return workspace.getConfiguration('emmet').update('includeLanguages', oldValueForIncludeLanguages || {}, ConfigurationTarget.Global); }); test('Expand html in completion list when inside script tag with javascript type if js is mapped to html (HTML)', async () => { @@ -399,7 +399,7 @@ suite('Tests for Expand Abbreviations (HTML)', () => { assert.strictEqual(((emmetCompletionItem.documentation) || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); return Promise.resolve(); }); - return workspace.getConfiguration('emmet').update('includeLanguages', oldValueForInlcudeLanguages || {}, ConfigurationTarget.Global); + return workspace.getConfiguration('emmet').update('includeLanguages', oldValueForIncludeLanguages || {}, ConfigurationTarget.Global); }); // test('No expanding when html is excluded in the settings', () => { diff --git a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts index 978d154ae97..571a11e69c9 100644 --- a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts +++ b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts @@ -104,7 +104,7 @@ suite('Tests for Wrap with Abbreviations', () => { const multiCursorsWithSelection = [new Selection(2, 2, 2, 28), new Selection(3, 2, 3, 33), new Selection(4, 6, 4, 36)]; const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 3, 33), new Selection(4, 0, 4, 36)]; - const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfile'); + const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfiles'); test('Wrap with block element using multi cursor', () => { return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests); diff --git a/extensions/emmet/tsconfig.json b/extensions/emmet/tsconfig.json index 994fa239537..ad07467f4ea 100644 --- a/extensions/emmet/tsconfig.json +++ b/extensions/emmet/tsconfig.json @@ -9,6 +9,7 @@ ], "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts" + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts", ] } diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index abceee2bc7d..add192a06c0 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -29,7 +29,7 @@ "jsonc-parser": "^2.2.1", "markdown-it": "^12.3.2", "parse5": "^3.0.2", - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.1.0" }, "contributes": { "jsonValidation": [ diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index ac87f5e2686..3aa5397f837 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -67,7 +67,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index c01c28355af..e3c3eca8d86 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "8825a76681cdc14801b5d9490372ff67ec6b9711" + "commitHash": "f74f4485011a9b463f1c1367195a4309a13403d6" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index f6cbec6c364..9475ac3389f 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.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/ionide/ionide-fsgrammar/commit/8825a76681cdc14801b5d9490372ff67ec6b9711", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/f74f4485011a9b463f1c1367195a4309a13403d6", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -958,6 +958,26 @@ } ] }, + { + "name": "binding.fsharp", + "begin": "\\b(use|use!|and|and!)\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", + "end": "\\s*(=)", + "beginCaptures": { + "1": { + "name": "keyword.fsharp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.fsharp" + } + }, + "patterns": [ + { + "include": "#common_binding_definition" + } + ] + }, { "name": "binding.fsharp", "begin": "(?<=with|and)\\s*\\b((get|set)\\s*(?=\\())(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", @@ -1275,11 +1295,11 @@ }, { "name": "constant.character.string.escape.fsharp", - "match": "\\\\([\\\\''ntbr]|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})" + "match": "\\\\(['\"\\\\abfnrtv]|([01][0-9][0-9]|2[0-4][0-9]|25[0-5])|(x[0-9a-fA-F]{2})|(u[0-9a-fA-F]{4})|(U00(0[0-9a-fA-F]|10)[0-9a-fA-F]{4}))" }, { - "name": "invalid.illeagal.character.string.fsharp", - "match": "\\\\(?![\\\\''ntbr]|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})." + "name": "invalid.illegal.character.string.fsharp", + "match": "\\\\(([0-9]{1,3})|(x[^\\s]{0,2})|(u[^\\s]{0,4})|(U[^\\s]{0,8})|[^\\s])" }, { "include": "#string_formatter" diff --git a/extensions/git-base/package.json b/extensions/git-base/package.json index 07e95e3a1ae..1c7daa3b4b9 100644 --- a/extensions/git-base/package.json +++ b/extensions/git-base/package.json @@ -100,7 +100,7 @@ ] }, "dependencies": { - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/git-base/src/remoteSource.ts b/extensions/git-base/src/remoteSource.ts index 83e83ae1fa9..134af197316 100644 --- a/extensions/git-base/src/remoteSource.ts +++ b/extensions/git-base/src/remoteSource.ts @@ -49,11 +49,12 @@ class RemoteSourceProviderQuickPick { @throttle private async query(): Promise { try { - const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || []; - this.ensureQuickPick(); + this.quickpick!.busy = true; this.quickpick!.show(); + const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || []; + if (remoteSources.length === 0) { this.quickpick!.items = [{ label: localize('none found', "No remote repositories found."), @@ -69,7 +70,7 @@ class RemoteSourceProviderQuickPick { })); } } catch (err) { - this.quickpick!.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }]; + this.quickpick!.items = [{ label: localize('error', "{0} Error: {1}", '$(error)', err.message), alwaysShow: true }]; console.error(err); } finally { this.quickpick!.busy = false; diff --git a/extensions/git-base/yarn.lock b/extensions/git-base/yarn.lock index 8fb6777123c..0cd0d3ce8f2 100644 --- a/extensions/git-base/yarn.lock +++ b/extensions/git-base/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.21.tgz#474d7589a30afcf5291f59bd49cca9ad171ffde4" integrity sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/git/package.json b/extensions/git/package.json index 00464a5d1e6..c0573f03bfa 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -8,15 +8,15 @@ "engines": { "vscode": "^1.5.0" }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ "diffCommand", "contribMergeEditorToolbar", "contribViewsWelcome", "scmActionButton", - "scmInput", "scmSelectedProvider", "scmValidation", + "tabInputTextMerge", "timeline" ], "categories": [ @@ -308,6 +308,18 @@ "category": "Git", "enablement": "!commitInProgress" }, + { + "command": "git.commitMessageAccept", + "title": "%command.commitMessageAccept%", + "icon": "$(check)", + "category": "Git" + }, + { + "command": "git.commitMessageDiscard", + "title": "%command.commitMessageDiscard%", + "icon": "$(discard)", + "category": "Git" + }, { "command": "git.restoreCommitTemplate", "title": "%command.restoreCommitTemplate%", @@ -316,7 +328,8 @@ { "command": "git.undoCommit", "title": "%command.undoCommit%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.checkout", @@ -587,6 +600,12 @@ { "command": "git.acceptMerge", "title": "%command.git.acceptMerge%", + "category": "Git", + "enablement": "isMergeEditor && mergeEditorResultUri in git.mergeChanges" + }, + { + "command": "git.openMergeEditor", + "title": "%command.git.openMergeEditor%", "category": "Git" } ], @@ -796,6 +815,14 @@ "command": "git.restoreCommitTemplate", "when": "false" }, + { + "command": "git.commitMessageAccept", + "when": "false" + }, + { + "command": "git.commitMessageDiscard", + "when": "false" + }, { "command": "git.revealInExplorer", "when": "false" @@ -1003,6 +1030,10 @@ { "command": "git.api.getRemoteSources", "when": "false" + }, + { + "command": "git.openMergeEditor", + "when": "false" } ], "scm/title": [ @@ -1479,7 +1510,22 @@ { "command": "git.openChange", "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file && scmActiveResourceHasChanges" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isMergeEditor && resourceScheme == file && scmActiveResourceHasChanges" + }, + { + "command": "git.openMergeEditor", + "group": "navigation@-10", + "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges" + }, + { + "command": "git.commitMessageAccept", + "group": "navigation", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" + }, + { + "command": "git.commitMessageDiscard", + "group": "navigation", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" }, { "command": "git.stageSelectedRanges", @@ -1517,7 +1563,7 @@ "merge/toolbar": [ { "command": "git.acceptMerge", - "when": "isMergeEditor && baseResourceScheme =~ /^git$|^file$/" + "when": "isMergeEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges" } ], "scm/change/title": [ @@ -2032,9 +2078,8 @@ }, "git.useEditorAsCommitInput": { "type": "boolean", - "scope": "resource", "description": "%config.useEditorAsCommitInput%", - "default": false + "default": true }, "git.verboseCommit": { "type": "boolean", @@ -2350,7 +2395,8 @@ "git.requireGitUserConfig": { "type": "boolean", "description": "%config.requireGitUserConfig%", - "default": true + "default": true, + "scope": "resource" }, "git.showCommitInput": { "type": "boolean", @@ -2360,10 +2406,14 @@ }, "git.terminalAuthentication": { "type": "boolean", - "scope": "resource", "default": true, "description": "%config.terminalAuthentication%" }, + "git.terminalGitEditor": { + "type": "boolean", + "default": false, + "description": "%config.terminalGitEditor%" + }, "git.useCommitInputAsStashMessage": { "type": "boolean", "scope": "resource", @@ -2404,20 +2454,29 @@ "description": "%config.timeline.showUncommitted%", "scope": "window" }, - "git.showUnpublishedCommitsButton": { - "type": "string", - "enum": [ - "always", - "whenEmpty", - "never" - ], - "enumDescriptions": [ - "%config.showUnpublishedCommitsButton.always%", - "%config.showUnpublishedCommitsButton.whenEmpty%", - "%config.showUnpublishedCommitsButton.never%" - ], - "default": "whenEmpty", - "description": "%config.showUnpublishedCommitsButton%", + "git.showActionButton": { + "type": "object", + "additionalProperties": false, + "description": "%config.showActionButton%", + "properties": { + "commit": { + "type": "boolean", + "description": "%config.showActionButton.commit%" + }, + "publish": { + "type": "boolean", + "description": "%config.showActionButton.publish%" + }, + "sync": { + "type": "boolean", + "description": "%config.showActionButton.sync%" + } + }, + "default": { + "commit": true, + "publish": true, + "sync": true + }, "scope": "resource" }, "git.statusLimit": { @@ -2426,19 +2485,6 @@ "default": 10000, "description": "%config.statusLimit%" }, - "git.experimental.installGuide": { - "type": "string", - "enum": [ - "default", - "download" - ], - "tags": [ - "experimental" - ], - "scope": "machine", - "description": "%config.experimental.installGuide%", - "default": "default" - }, "git.repositoryScanIgnoredFolders": { "type": "array", "items": { @@ -2462,7 +2508,6 @@ "type": "string" }, "default": [], - "scope": "resource", "markdownDescription": "%config.commandsToLog%" }, "git.logLevel": { @@ -2489,14 +2534,11 @@ "markdownDescription": "%config.logLevel%", "scope": "window" }, - "git.experimental.mergeEditor": { + "git.mergeEditor": { "type": "boolean", - "default": false, - "markdownDescription": "%config.experimental.mergeEditor%", - "scope": "window", - "tags": [ - "experimental" - ] + "default": true, + "markdownDescription": "%config.mergeEditor%", + "scope": "window" } } }, @@ -2619,56 +2661,51 @@ "contents": "%view.workbench.scm.disabled%", "when": "!config.git.enabled" }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.mac%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isMac" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.windows%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isWindows" - }, - { - "view": "scm", - "contents": "%view.workbench.scm.missing.guide.linux%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isLinux" - }, { "view": "scm", "contents": "%view.workbench.scm.missing%", - "when": "config.git.enabled && git.missing && config.git.experimental.installGuide == default" + "when": "config.git.enabled && git.missing" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.mac%", + "when": "config.git.enabled && git.missing && isMac" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.windows%", + "when": "config.git.enabled && git.missing && isWindows" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.missing.linux%", + "when": "config.git.enabled && git.missing && isLinux" }, { "view": "scm", "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && workbenchState == empty", + "when": "config.git.enabled && !git.missing && workbenchState == empty", "enablement": "git.state == initialized", "group": "2_open@1" }, { "view": "scm", "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && workbenchState == folder", + "when": "config.git.enabled && !git.missing && workbenchState == folder", "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount != 0", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0", "enablement": "git.state == initialized", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.emptyWorkspace%", - "when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount == 0", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0", "enablement": "git.state == initialized", "group": "2_open@1" }, @@ -2690,19 +2727,18 @@ }, "dependencies": { "@joaomoreno/unique-names-generator": "5.0.0", - "@vscode/extension-telemetry": "0.4.10", + "@vscode/extension-telemetry": "0.6.2", "@vscode/iconv-lite-umd": "0.7.0", "byline": "^5.0.0", - "file-type": "^7.2.0", + "file-type": "16.5.4", "jschardet": "3.0.0", "picomatch": "2.3.1", - "vscode-nls": "^4.0.0", + "vscode-nls": "^5.1.0", "vscode-uri": "^2.0.0", "which": "^1.3.0" }, "devDependencies": { "@types/byline": "4.2.31", - "@types/file-type": "^5.2.1", "@types/mocha": "^9.1.1", "@types/node": "16.x", "@types/picomatch": "2.3.0", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5210e64de80..5fc0ec7e5d7 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -46,6 +46,8 @@ "command.commitAllNoVerify": "Commit All (No Verify)", "command.commitAllSignedNoVerify": "Commit All (Signed Off, No Verify)", "command.commitAllAmendNoVerify": "Commit All (Amend, No Verify)", + "command.commitMessageAccept": "Accept Commit Message", + "command.commitMessageDiscard": "Discard Commit Message", "command.restoreCommitTemplate": "Restore Commit Template", "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", @@ -101,6 +103,7 @@ "command.api.getRepositoryState": "Get Repository State", "command.api.getRemoteSources": "Get Remote Sources", "command.git.acceptMerge": "Accept Merge", + "command.git.openMergeEditor": "Open in Merge Editor", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows). This can also be an array of string values containing multiple paths to look up.", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -140,7 +143,7 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.ignoreRebaseWarning": "Ignores the warning when it looks like the branch might have been rebased when pulling.", "config.defaultCloneDirectory": "The default location to clone a git repository.", - "config.useEditorAsCommitInput": "Use an editor to author the commit message.", + "config.useEditorAsCommitInput": "Controls whether a full text editor will be used to author commit messages, whenever no message is provided in the commit input box.", "config.verboseCommit": "Enable verbose output when `#git.useEditorAsCommitInput#` is enabled.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", @@ -212,16 +215,17 @@ "config.requireGitUserConfig": "Controls whether to require explicit Git user configuration or allow Git to guess if missing.", "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.", + "config.terminalGitEditor": "Controls whether to enable VS Code to be git editor for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.", "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", "config.timeline.date.committed": "Use the committed date", "config.timeline.date.authored": "Use the authored date", "config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.", - "config.showUnpublishedCommitsButton": "Controls whether to show an action button to sync or publish, if there are unpublished commits.", - "config.showUnpublishedCommitsButton.always": "Always shows the action button, if there are unpublished commits.", - "config.showUnpublishedCommitsButton.whenEmpty": "Only shows the action button if there are no other changes and there are unpublished commits.", - "config.showUnpublishedCommitsButton.never": "Never shows the action button.", + "config.showActionButton": "Controls whether an action button is shown in the Source Control view.", + "config.showActionButton.commit": "Show an action button to commit changes when the local branch has modified files ready to be committed.", + "config.showActionButton.publish": "Show an action button to publish the local branch when it does not have a tracking remote branch.", + "config.showActionButton.sync": "Show an action button to synchronize changes when the local branch is either ahead or behind the remote branch.", "config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.", "config.experimental.installGuide": "Experimental improvements for the git setup flow.", "config.repositoryScanIgnoredFolders": "List of folders that are ignored while scanning for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`.", @@ -242,7 +246,7 @@ "config.logLevel.error": "Log only error, and critical information", "config.logLevel.critical": "Log only critical information", "config.logLevel.off": "Log nothing", - "config.experimental.mergeEditor": "Open the _experimental_ merge editor for files that are currently under conflict.", + "config.mergeEditor": "Open the merge editor for files that are currently under conflict.", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", @@ -263,15 +267,7 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources.", - "view.workbench.scm.missing": { - "message": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", - "comment": [ - "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "view.workbench.scm.missing.guide.windows": { + "view.workbench.scm.missing.windows": { "message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -279,7 +275,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide.mac": { + "view.workbench.scm.missing.mac": { "message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -287,7 +283,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide.linux": { + "view.workbench.scm.missing.linux": { "message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).", "comment": [ "{Locked='](command:workbench.action.reloadWindow'}", @@ -295,7 +291,7 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.scm.missing.guide": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", + "view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).", "view.workbench.scm.disabled": { "message": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "comment": [ diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index d8b45a00c26..69b24957282 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -3,18 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode'; import * as nls from 'vscode-nls'; +import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode'; +import { ApiRepository } from './api/api1'; +import { Branch, Status } from './api/git'; +import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; import { Repository, Operation } from './repository'; import { dispose } from './util'; -import { Branch } from './api/git'; const localize = nls.loadMessageBundle(); interface ActionButtonState { readonly HEAD: Branch | undefined; - readonly isSyncRunning: boolean; - readonly repositoryHasNoChanges: boolean; + readonly isCommitInProgress: boolean; + readonly isMergeInProgress: boolean; + readonly isRebaseInProgress: boolean; + readonly isSyncInProgress: boolean; + readonly repositoryHasChangesToCommit: boolean; } export class ActionButtonCommand { @@ -32,81 +37,267 @@ export class ActionButtonCommand { private disposables: Disposable[] = []; - constructor(readonly repository: Repository) { - this._state = { HEAD: undefined, isSyncRunning: false, repositoryHasNoChanges: false }; + constructor( + readonly repository: Repository, + readonly postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry) { + this._state = { + HEAD: undefined, + isCommitInProgress: false, + isMergeInProgress: false, + isRebaseInProgress: false, + isSyncInProgress: false, + repositoryHasChangesToCommit: false + }; repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); + + this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire())); + + const root = Uri.file(repository.root); + this.disposables.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('git.enableSmartCommit', root) || + e.affectsConfiguration('git.smartCommitChanges', root) || + e.affectsConfiguration('git.suggestSmartCommit', root)) { + this.onDidChangeSmartCommitSettings(); + } + + if (e.affectsConfiguration('git.branchProtection', root) || + e.affectsConfiguration('git.branchProtectionPrompt', root) || + e.affectsConfiguration('git.postCommitCommand', root) || + e.affectsConfiguration('git.showActionButton', root)) { + this._onDidChange.fire(); + } + })); } get button(): SourceControlActionButton | undefined { - if (!this.state.HEAD || !this.state.HEAD.name || !this.state.HEAD.commit) { return undefined; } - - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const showActionButton = config.get('showUnpublishedCommitsButton', 'whenEmpty'); - const postCommitCommand = config.get('postCommitCommand'); - const noPostCommitCommand = postCommitCommand !== 'sync' && postCommitCommand !== 'push'; + if (!this.state.HEAD) { return undefined; } let actionButton: SourceControlActionButton | undefined; - if (showActionButton === 'always' || (showActionButton === 'whenEmpty' && this.state.repositoryHasNoChanges && noPostCommitCommand)) { - if (this.state.HEAD.upstream) { - if (this.state.HEAD.ahead) { - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const rebaseWhenSync = config.get('rebaseWhenSync'); - const ahead = `${this.state.HEAD.ahead}$(arrow-up)`; - const behind = this.state.HEAD.behind ? `${this.state.HEAD.behind}$(arrow-down) ` : ''; - const icon = this.state.isSyncRunning ? '$(sync~spin)' : '$(sync)'; + if (this.state.repositoryHasChangesToCommit) { + // Commit Changes (enabled) + actionButton = this.getCommitActionButton(); + } - actionButton = { - command: { - command: this.state.isSyncRunning ? '' : rebaseWhenSync ? 'git.syncRebase' : 'git.sync', - title: localize('scm button sync title', "{0} {1}{2}", icon, behind, ahead), - tooltip: this.state.isSyncRunning ? - localize('syncing changes', "Synchronizing Changes...") - : this.repository.syncTooltip, - arguments: [this.repository.sourceControl], - }, - description: localize('scm button sync description', "{0} Sync Changes {1}{2}", icon, behind, ahead) - }; + // Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled) + return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton(); + } + + private getCommitActionButton(): SourceControlActionButton | undefined { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true }); + + // The button is disabled + if (!showActionButton.commit) { return undefined; } + + return { + command: this.getCommitActionButtonPrimaryCommand(), + secondaryCommands: this.getCommitActionButtonSecondaryCommands(), + enabled: (this.state.repositoryHasChangesToCommit || this.state.isRebaseInProgress) && !this.state.isCommitInProgress && !this.state.isMergeInProgress + }; + } + + private getCommitActionButtonPrimaryCommand(): Command { + // Rebase Continue + if (this.state.isRebaseInProgress) { + return { + command: 'git.commit', + title: localize('scm button continue title', "{0} Continue", '$(check)'), + tooltip: this.state.isCommitInProgress ? localize('scm button continuing tooltip', "Continuing Rebase...") : localize('scm button continue tooltip', "Continue Rebase"), + arguments: [this.repository.sourceControl, ''] + }; + } + + // Commit + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const postCommitCommand = config.get('postCommitCommand'); + + // Branch protection + const isBranchProtected = this.repository.isBranchProtected(); + const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; + const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; + const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; + + // Icon + const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; + + let commandArg = ''; + let title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)'); + let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); + + // Title, tooltip + switch (postCommitCommand) { + case 'push': { + commandArg = 'git.push'; + title = localize('scm button commit and push title', "{0} Commit & Push", icon ?? '$(arrow-up)'); + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch and pushing tooltip', "Committing to New Branch & Pushing Changes...") : + localize('scm button commit to new branch and push tooltip', "Commit to New Branch & Push Changes"); + } else { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing and pushing tooltip', "Committing & Pushing Changes...") : + localize('scm button commit and push tooltip', "Commit & Push Changes"); } - } else { - actionButton = { - command: { - command: this.state.isSyncRunning ? '' : 'git.publish', - title: localize('scm button publish title', "$(cloud-upload) Publish Branch"), - tooltip: this.state.isSyncRunning ? - localize('scm button publish branch running', "Publishing Branch...") : - localize('scm button publish branch', "Publish Branch"), - arguments: [this.repository.sourceControl], - } - }; + break; + } + case 'sync': { + commandArg = 'git.sync'; + title = localize('scm button commit and sync title', "{0} Commit & Sync", icon ?? '$(sync)'); + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch and synching tooltip', "Committing to New Branch & Synching Changes...") : + localize('scm button commit to new branch and sync tooltip', "Commit to New Branch & Sync Changes"); + } else { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing and synching tooltip', "Committing & Synching Changes...") : + localize('scm button commit and sync tooltip', "Commit & Sync Changes"); + } + break; + } + default: { + if (alwaysCommitToNewBranch) { + tooltip = this.state.isCommitInProgress ? + localize('scm button committing to new branch tooltip', "Committing Changes to New Branch...") : + localize('scm button commit to new branch tooltip', "Commit Changes to New Branch"); + } + break; } } - return actionButton; + return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] }; + } + + private getCommitActionButtonSecondaryCommands(): Command[][] { + const commandGroups: Command[][] = []; + + if (!this.state.isRebaseInProgress) { + for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) { + const commands = provider.getCommands(new ApiRepository(this.repository)); + commandGroups.push((commands ?? []).map(c => { + return { + command: 'git.commit', + title: c.title, + arguments: [this.repository.sourceControl, c.command] + }; + })); + } + + if (commandGroups.length > 0) { + commandGroups[0].splice(0, 0, { + command: 'git.commit', + title: localize('scm secondary button commit', "Commit"), + arguments: [this.repository.sourceControl, ''] + }); + } + } + + return commandGroups; + } + + private getPublishBranchActionButton(): SourceControlActionButton | undefined { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); + + // Branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled + if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; } + + return { + command: { + command: 'git.publish', + title: localize({ key: 'scm publish branch action button title', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "{0} Publish Branch", '$(cloud-upload)'), + tooltip: this.state.isSyncInProgress ? + localize({ key: 'scm button publish branch running', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publishing Branch...") : + localize({ key: 'scm button publish branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publish Branch"), + arguments: [this.repository.sourceControl], + }, + enabled: !this.state.isSyncInProgress + }; + } + + private getSyncChangesActionButton(): SourceControlActionButton | undefined { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true }); + const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0; + + // Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled + if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; } + + const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : ''; + const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : ''; + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)'; + + return { + command: { + command: 'git.sync', + title: `${icon}${behind}${ahead}`, + tooltip: this.state.isSyncInProgress ? + localize('syncing changes', "Synchronizing Changes...") + : this.repository.syncTooltip, + arguments: [this.repository.sourceControl], + }, + description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead), + enabled: !this.state.isSyncInProgress + }; } private onDidChangeOperations(): void { - const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) || + const isCommitInProgress = + this.repository.operations.isRunning(Operation.Commit) || + this.repository.operations.isRunning(Operation.RebaseContinue); + + const isSyncInProgress = + this.repository.operations.isRunning(Operation.Sync) || this.repository.operations.isRunning(Operation.Push) || this.repository.operations.isRunning(Operation.Pull); - this.state = { ...this.state, isSyncRunning }; + this.state = { ...this.state, isCommitInProgress, isSyncInProgress }; + } + + private onDidChangeSmartCommitSettings(): void { + this.state = { + ...this.state, + repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() + }; } private onDidRunGitStatus(): void { this.state = { ...this.state, HEAD: this.repository.HEAD, - repositoryHasNoChanges: - this.repository.indexGroup.resourceStates.length === 0 && - this.repository.mergeGroup.resourceStates.length === 0 && - this.repository.untrackedGroup.resourceStates.length === 0 && - this.repository.workingTreeGroup.resourceStates.length === 0 + isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0, + isRebaseInProgress: !!this.repository.rebaseCommit, + repositoryHasChangesToCommit: this.repositoryHasChangesToCommit() }; } + private repositoryHasChangesToCommit(): boolean { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const enableSmartCommit = config.get('enableSmartCommit') === true; + const suggestSmartCommit = config.get('suggestSmartCommit') === true; + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all'); + + const resources = [...this.repository.indexGroup.resourceStates]; + + if ( + // Smart commit enabled (all) + (enableSmartCommit && smartCommitChanges === 'all') || + // Smart commit disabled, smart suggestion enabled + (!enableSmartCommit && suggestSmartCommit) + ) { + resources.push(...this.repository.workingTreeGroup.resourceStates); + } + + // Smart commit enabled (tracked only) + if (enableSmartCommit && smartCommitChanges === 'tracked') { + resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED)); + } + + return resources.length !== 0; + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 32417c2a6d1..4581eaa1abc 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode'; import { combinedDisposable, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -57,157 +57,157 @@ export class ApiRepositoryUIState implements RepositoryUIState { export class ApiRepository implements Repository { - readonly rootUri: Uri = Uri.file(this._repository.root); - readonly inputBox: InputBox = new ApiInputBox(this._repository.inputBox); - readonly state: RepositoryState = new ApiRepositoryState(this._repository); - readonly ui: RepositoryUIState = new ApiRepositoryUIState(this._repository.sourceControl); + readonly rootUri: Uri = Uri.file(this.repository.root); + readonly inputBox: InputBox = new ApiInputBox(this.repository.inputBox); + readonly state: RepositoryState = new ApiRepositoryState(this.repository); + readonly ui: RepositoryUIState = new ApiRepositoryUIState(this.repository.sourceControl); - constructor(private _repository: BaseRepository) { } + constructor(readonly repository: BaseRepository) { } apply(patch: string, reverse?: boolean): Promise { - return this._repository.apply(patch, reverse); + return this.repository.apply(patch, reverse); } getConfigs(): Promise<{ key: string; value: string }[]> { - return this._repository.getConfigs(); + return this.repository.getConfigs(); } getConfig(key: string): Promise { - return this._repository.getConfig(key); + return this.repository.getConfig(key); } setConfig(key: string, value: string): Promise { - return this._repository.setConfig(key, value); + return this.repository.setConfig(key, value); } getGlobalConfig(key: string): Promise { - return this._repository.getGlobalConfig(key); + return this.repository.getGlobalConfig(key); } getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }> { - return this._repository.getObjectDetails(treeish, path); + return this.repository.getObjectDetails(treeish, path); } detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> { - return this._repository.detectObjectType(object); + return this.repository.detectObjectType(object); } buffer(ref: string, filePath: string): Promise { - return this._repository.buffer(ref, filePath); + return this.repository.buffer(ref, filePath); } show(ref: string, path: string): Promise { - return this._repository.show(ref, path); + return this.repository.show(ref, path); } getCommit(ref: string): Promise { - return this._repository.getCommit(ref); + return this.repository.getCommit(ref); } add(paths: string[]) { - return this._repository.add(paths.map(p => Uri.file(p))); + return this.repository.add(paths.map(p => Uri.file(p))); } revert(paths: string[]) { - return this._repository.revert(paths.map(p => Uri.file(p))); + return this.repository.revert(paths.map(p => Uri.file(p))); } clean(paths: string[]) { - return this._repository.clean(paths.map(p => Uri.file(p))); + return this.repository.clean(paths.map(p => Uri.file(p))); } diff(cached?: boolean) { - return this._repository.diff(cached); + return this.repository.diff(cached); } diffWithHEAD(): Promise; diffWithHEAD(path: string): Promise; diffWithHEAD(path?: string): Promise { - return this._repository.diffWithHEAD(path); + return this.repository.diffWithHEAD(path); } diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string): Promise { - return this._repository.diffWith(ref, path); + return this.repository.diffWith(ref, path); } diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; diffIndexWithHEAD(path?: string): Promise { - return this._repository.diffIndexWithHEAD(path); + return this.repository.diffIndexWithHEAD(path); } diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string): Promise { - return this._repository.diffIndexWith(ref, path); + return this.repository.diffIndexWith(ref, path); } diffBlobs(object1: string, object2: string): Promise { - return this._repository.diffBlobs(object1, object2); + return this.repository.diffBlobs(object1, object2); } diffBetween(ref1: string, ref2: string): Promise; diffBetween(ref1: string, ref2: string, path: string): Promise; diffBetween(ref1: string, ref2: string, path?: string): Promise { - return this._repository.diffBetween(ref1, ref2, path); + return this.repository.diffBetween(ref1, ref2, path); } hashObject(data: string): Promise { - return this._repository.hashObject(data); + return this.repository.hashObject(data); } createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise { - return this._repository.branch(name, checkout, ref); + return this.repository.branch(name, checkout, ref); } deleteBranch(name: string, force?: boolean): Promise { - return this._repository.deleteBranch(name, force); + return this.repository.deleteBranch(name, force); } getBranch(name: string): Promise { - return this._repository.getBranch(name); + return this.repository.getBranch(name); } getBranches(query: BranchQuery): Promise { - return this._repository.getBranches(query); + return this.repository.getBranches(query); } setBranchUpstream(name: string, upstream: string): Promise { - return this._repository.setBranchUpstream(name, upstream); + return this.repository.setBranchUpstream(name, upstream); } getMergeBase(ref1: string, ref2: string): Promise { - return this._repository.getMergeBase(ref1, ref2); + return this.repository.getMergeBase(ref1, ref2); } tag(name: string, upstream: string): Promise { - return this._repository.tag(name, upstream); + return this.repository.tag(name, upstream); } deleteTag(name: string): Promise { - return this._repository.deleteTag(name); + return this.repository.deleteTag(name); } status(): Promise { - return this._repository.status(); + return this.repository.status(); } checkout(treeish: string): Promise { - return this._repository.checkout(treeish); + return this.repository.checkout(treeish); } addRemote(name: string, url: string): Promise { - return this._repository.addRemote(name, url); + return this.repository.addRemote(name, url); } removeRemote(name: string): Promise { - return this._repository.removeRemote(name); + return this.repository.removeRemote(name); } renameRemote(name: string, newName: string): Promise { - return this._repository.renameRemote(name, newName); + return this.repository.renameRemote(name, newName); } fetch(arg0?: FetchOptions | string | undefined, @@ -216,30 +216,30 @@ export class ApiRepository implements Repository { prune?: boolean | undefined ): Promise { if (arg0 !== undefined && typeof arg0 !== 'string') { - return this._repository.fetch(arg0); + return this.repository.fetch(arg0); } - return this._repository.fetch({ remote: arg0, ref, depth, prune }); + return this.repository.fetch({ remote: arg0, ref, depth, prune }); } pull(unshallow?: boolean): Promise { - return this._repository.pull(undefined, unshallow); + return this.repository.pull(undefined, unshallow); } push(remoteName?: string, branchName?: string, setUpstream: boolean = false, force?: ForcePushMode): Promise { - return this._repository.pushTo(remoteName, branchName, setUpstream, force); + return this.repository.pushTo(remoteName, branchName, setUpstream, force); } blame(path: string): Promise { - return this._repository.blame(path); + return this.repository.blame(path); } log(options?: LogOptions): Promise { - return this._repository.log(options); + return this.repository.log(options); } commit(message: string, opts?: CommitOptions): Promise { - return this._repository.commit(message, opts); + return this.repository.commit(message, opts); } } @@ -318,6 +318,10 @@ export class ApiImpl implements API { return this._model.registerCredentialsProvider(provider); } + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { + return this._model.registerPostCommitCommandsProvider(provider); + } + registerPushErrorHandler(handler: PushErrorHandler): Disposable { return this._model.registerPushErrorHandler(handler); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 14c7447e3e8..cb6265558df 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, Event, Disposable, ProviderResult } from 'vscode'; +import { Uri, Event, Disposable, ProviderResult, Command } from 'vscode'; export { ProviderResult } from 'vscode'; export interface Git { @@ -139,6 +139,7 @@ export interface CommitOptions { requireUserConfig?: boolean; useEditor?: boolean; verbose?: boolean; + postCommitCommand?: string; } export interface FetchOptions { @@ -253,6 +254,10 @@ export interface CredentialsProvider { getCredentials(host: Uri): ProviderResult; } +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } @@ -281,6 +286,7 @@ export interface API { registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; registerPushErrorHandler(handler: PushErrorHandler): Disposable; } diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index d9d9378f8aa..cbf981eea19 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -8,9 +8,11 @@ import { IDisposable, EmptyDisposable, toDisposable } from './util'; import * as path from 'path'; import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; import { CredentialsProvider, Credentials } from './api/git'; +import { ITerminalEnvironmentProvider } from './terminal'; -export class Askpass implements IIPCHandler { +export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { + private env: { [key: string]: string }; private disposable: IDisposable = EmptyDisposable; private cache = new Map(); private credentialsProviders = new Set(); @@ -19,6 +21,13 @@ export class Askpass implements IIPCHandler { if (ipc) { this.disposable = ipc.registerHandler('askpass', this); } + + this.env = { + GIT_ASKPASS: path.join(__dirname, this.ipc ? 'askpass.sh' : 'askpass-empty.sh'), + VSCODE_GIT_ASKPASS_NODE: process.execPath, + VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', + VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'), + }; } async handle({ request, host }: { request: string; host: string }): Promise { @@ -64,25 +73,13 @@ export class Askpass implements IIPCHandler { } getEnv(): { [key: string]: string } { - if (!this.ipc) { - return { - GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') - }; - } - - const env: { [key: string]: string } = { - ...this.ipc.getEnv(), - VSCODE_GIT_ASKPASS_NODE: process.execPath, - VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', - VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js') - }; - const config = workspace.getConfiguration('git'); - if (config.get('useIntegratedAskPass')) { - env.GIT_ASKPASS = path.join(__dirname, 'askpass.sh'); - } + return config.get('useIntegratedAskPass') ? this.env : {}; + } - return env; + getTerminalEnv(): { [key: string]: string } { + const config = workspace.getConfiguration('git'); + return config.get('useIntegratedAskPass') && config.get('terminalAuthentication') ? this.env : {}; } registerCredentialsProvider(provider: CredentialsProvider): Disposable { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 1f134c56866..656a18e885c 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,8 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import * as picomatch from 'picomatch'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import * as nls from 'vscode-nls'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; @@ -27,24 +26,26 @@ const localize = nls.loadMessageBundle(); class CheckoutItem implements QuickPickItem { protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get label(): string { return this.ref.name || this.shortCommit; } + get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } + get refName(): string | undefined { return this.ref.name; } - constructor(protected ref: Ref) { } + constructor(protected repository: Repository, protected ref: Ref) { } - async run(repository: Repository, opts?: { detached?: boolean }): Promise { + async run(opts?: { detached?: boolean }): Promise { const ref = this.ref.name; if (!ref) { return; } - await repository.checkout(ref, opts); + await this.repository.checkout(ref, opts); } } class CheckoutTagItem extends CheckoutItem { + override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; } override get description(): string { return localize('tag at', "Tag at {0}", this.shortCommit); } @@ -52,21 +53,22 @@ class CheckoutTagItem extends CheckoutItem { class CheckoutRemoteHeadItem extends CheckoutItem { + override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; } override get description(): string { return localize('remote branch at', "Remote branch at {0}", this.shortCommit); } - override async run(repository: Repository, opts?: { detached?: boolean }): Promise { + override async run(opts?: { detached?: boolean }): Promise { if (!this.ref.name) { return; } - const branches = await repository.findTrackingBranches(this.ref.name); + const branches = await this.repository.findTrackingBranches(this.ref.name); if (branches.length > 0) { - await repository.checkout(branches[0].name!, opts); + await this.repository.checkout(branches[0].name!, opts); } else { - await repository.checkoutTracking(this.ref.name, opts); + await this.repository.checkoutTracking(this.ref.name, opts); } } } @@ -139,6 +141,7 @@ class HEADItem implements QuickPickItem { get label(): string { return 'HEAD'; } get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); } get alwaysShow(): boolean { return true; } + get refName(): string { return 'HEAD'; } } class AddRemoteItem implements QuickPickItem { @@ -219,7 +222,7 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { checkoutTypes = checkoutTypeConfig; } - const processors = checkoutTypes.map(getCheckoutProcessor) + const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type)) .filter(p => !!p) as CheckoutProcessor[]; for (const ref of repository.refs) { @@ -234,8 +237,8 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { class CheckoutProcessor { private refs: Ref[] = []; - get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(r)); } - constructor(private type: RefType, private ctor: { new(ref: Ref): CheckoutItem }) { } + get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(this.repository, r)); } + constructor(private repository: Repository, private type: RefType, private ctor: { new(repository: Repository, ref: Ref): CheckoutItem }) { } onRef(ref: Ref): void { if (ref.type === this.type) { @@ -244,14 +247,14 @@ class CheckoutProcessor { } } -function getCheckoutProcessor(type: string): CheckoutProcessor | undefined { +function getCheckoutProcessor(repository: Repository, type: string): CheckoutProcessor | undefined { switch (type) { case 'local': - return new CheckoutProcessor(RefType.Head, CheckoutItem); + return new CheckoutProcessor(repository, RefType.Head, CheckoutItem); case 'remote': - return new CheckoutProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem); + return new CheckoutProcessor(repository, RefType.RemoteHead, CheckoutRemoteHeadItem); case 'tags': - return new CheckoutProcessor(RefType.Tag, CheckoutTagItem); + return new CheckoutProcessor(repository, RefType.Tag, CheckoutTagItem); } return undefined; @@ -405,7 +408,7 @@ export class CommandCenter { } } - @command('_git.openMergeEditor') + @command('git.openMergeEditor') async openMergeEditor(uri: unknown) { if (!(uri instanceof Uri)) { return; @@ -415,21 +418,25 @@ export class CommandCenter { return; } + const isRebasing = Boolean(repo.rebaseCommit); - type InputData = { uri: Uri; detail?: string; description?: string }; + type InputData = { uri: Uri; title?: string; detail?: string; description?: string }; const mergeUris = toMergeUris(uri); - const input1: InputData = { uri: mergeUris.ours }; - const input2: InputData = { uri: mergeUris.theirs }; + const current: InputData = { uri: mergeUris.ours, title: localize('Current', 'Current') }; + const incoming: InputData = { uri: mergeUris.theirs, title: localize('Incoming', 'Incoming') }; try { - const [head, mergeHead] = await Promise.all([repo.getCommit('HEAD'), repo.getCommit('MERGE_HEAD')]); + const [head, rebaseOrMergeHead] = await Promise.all([ + repo.getCommit('HEAD'), + isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD') + ]); // ours (current branch and commit) - input1.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); - input1.description = head.hash.substring(0, 7); + current.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); + current.description = '$(git-commit) ' + head.hash.substring(0, 7); // theirs - input2.detail = mergeHead.refNames.join(', '); - input2.description = mergeHead.hash.substring(0, 7); + incoming.detail = rebaseOrMergeHead.refNames.join(', '); + incoming.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); } catch (error) { // not so bad, can continue with just uris @@ -438,9 +445,9 @@ export class CommandCenter { } const options = { - ancestor: mergeUris.base, - input1, - input2, + base: mergeUris.base, + input1: isRebasing ? current : incoming, + input2: isRebasing ? incoming : current, output: uri }; @@ -462,7 +469,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); @@ -481,6 +488,7 @@ export class CommandCenter { canSelectFolders: true, canSelectMany: false, defaultUri: Uri.file(defaultCloneDirectory), + title: localize('selectFolderTitle', "Choose a folder to clone {0} into", url), openLabel: localize('selectFolder', "Select Repository Location") }); @@ -488,7 +496,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); @@ -547,8 +555,8 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); @@ -567,7 +575,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); @@ -577,7 +585,7 @@ export class CommandCenter { /* __GDPR__ "clone" : { "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } } */ this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); @@ -1096,20 +1104,33 @@ export class CommandCenter { return; } + const { activeTab } = window.tabGroups.activeTabGroup; + if (!activeTab) { + return; + } + + // make sure to save the merged document const doc = workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString()); if (!doc) { console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't match a document`); return; } + if (doc.isDirty) { + await doc.save(); + } - await doc.save(); - await repository.add([uri]); + // find the merge editor tabs for the resource in question and close them all + let didCloseTab = false; + const mergeEditorTabs = window.tabGroups.all.map(group => group.tabs.filter(tab => tab.input instanceof TabInputTextMerge && tab.input.result.toString() === uri.toString())).flat(); + if (mergeEditorTabs.includes(activeTab)) { + didCloseTab = await window.tabGroups.close(mergeEditorTabs, true); + } - // TODO@jrieken there isn't a `TabInputTextMerge` instance yet, till now the merge editor - // uses the `TabInputText` for the out-resource and we use that to identify and CLOSE the tab - const { activeTab } = window.tabGroups.activeTabGroup; - if (activeTab && activeTab?.input instanceof TabInputText && activeTab.input.uri.toString() === uri.toString()) { - await window.tabGroups.close(activeTab, true); + // Only stage if the merge editor has been successfully closed. That means all conflicts have been + // handled or unhandled conflicts are OK by the user. + if (didCloseTab) { + await repository.add([uri]); + await commands.executeCommand('workbench.view.scm'); } } @@ -1432,7 +1453,7 @@ export class CommandCenter { private async smartCommit( repository: Repository, getCommitMessage: () => Promise, - opts?: CommitOptions + opts: CommitOptions ): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); @@ -1478,14 +1499,8 @@ export class CommandCenter { } } - if (!opts) { - opts = { all: noStagedChanges }; - } else if (!opts.all && noStagedChanges && !opts.empty) { - opts = { ...opts, all: true }; - } - // no changes, and the user has not configured to commit all in this case - if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty) { + if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) { const suggestSmartCommit = config.get('suggestSmartCommit') === true; if (!suggestSmartCommit) { @@ -1509,6 +1524,12 @@ export class CommandCenter { } } + if (opts.all === undefined) { + opts = { ...opts, all: noStagedChanges }; + } else if (!opts.all && noStagedChanges && !opts.empty) { + opts = { ...opts, all: true }; + } + // enable signing of commits if configured opts.signCommit = enableCommitSigning; @@ -1538,6 +1559,8 @@ export class CommandCenter { // amend allows changing only the commit message && !opts.amend && !opts.empty + // rebase not in progress + && repository.rebaseCommit === undefined ) { const commitAnyway = localize('commit anyway', "Create Empty Commit"); const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway); @@ -1584,11 +1607,8 @@ export class CommandCenter { } // Branch protection - const branchProtection = config.get('branchProtection')!.map(bp => bp.trim()).filter(bp => bp !== ''); const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; - const branchIsProtected = branchProtection.some(bp => picomatch.isMatch(repository.HEAD?.name ?? '', bp)); - - if (branchIsProtected && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) { + if (repository.isBranchProtected() && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) { const commitToNewBranch = localize('commit to branch', "Commit to a New Branch"); let pick: string | undefined = commitToNewBranch; @@ -1615,21 +1635,24 @@ export class CommandCenter { await repository.commit(message, opts); - const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); + // Execute post-commit command + let postCommitCommand = opts.postCommitCommand; - switch (postCommitCommand) { - case 'push': - await this._push(repository, { pushType: PushType.Push, silent: true }); - break; - case 'sync': - await this.sync(repository); - break; + if (postCommitCommand === undefined) { + // Commit WAS NOT initiated using the action button (ex: keybinding, toolbar + // action, command palette) so we honour the `git.postCommitCommand` setting. + const postCommitCommandSetting = config.get('postCommitCommand'); + postCommitCommand = postCommitCommandSetting === 'push' || postCommitCommandSetting === 'sync' ? `git.${postCommitCommandSetting}` : ''; + } + + if (postCommitCommand.length) { + await commands.executeCommand(postCommitCommand, new ApiRepository(repository)); } return true; } - private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise { + private async commitWithAnyInput(repository: Repository, opts: CommitOptions): Promise { const message = repository.inputBox.value; const root = Uri.file(repository.root); const config = workspace.getConfiguration('git', root); @@ -1672,8 +1695,8 @@ export class CommandCenter { } @command('git.commit', { repository: true }) - async commit(repository: Repository): Promise { - await this.commitWithAnyInput(repository); + async commit(repository: Repository, postCommitCommand?: string): Promise { + await this.commitWithAnyInput(repository, { postCommitCommand }); } @command('git.commitStaged', { repository: true }) @@ -1706,13 +1729,58 @@ export class CommandCenter { await this.commitWithAnyInput(repository, { all: true, amend: true }); } + @command('git.commitMessageAccept') + async commitMessageAccept(arg?: Uri): Promise { + if (!arg) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + @command('git.commitMessageDiscard') + async commitMessageDiscard(arg?: Uri): Promise { + if (!arg) { return; } + + // Clear the contents of the editor + const editors = window.visibleTextEditors + .filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg.toString()); + + if (editors.length !== 1) { return; } + + const commitMsgEditor = editors[0]; + const commitMsgDocument = commitMsgEditor.document; + + const editResult = await commitMsgEditor.edit(builder => { + const firstLine = commitMsgDocument.lineAt(0); + const lastLine = commitMsgDocument.lineAt(commitMsgDocument.lineCount - 1); + + builder.delete(new Range(firstLine.range.start, lastLine.range.end)); + }); + + if (!editResult) { return; } + + // Save the document + const saveResult = await commitMsgDocument.save(); + if (!saveResult) { return; } + + // Close the tab + this._closeEditorTab(arg); + } + + private _closeEditorTab(uri: Uri): void { + const tabToClose = window.tabGroups.all.map(g => g.tabs).flat() + .filter(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString()); + + window.tabGroups.close(tabToClose); + } + private async _commitEmpty(repository: Repository, noVerify?: boolean): Promise { const root = Uri.file(repository.root); const config = workspace.getConfiguration('git', root); const shouldPrompt = config.get('confirmEmptyCommits') === true; if (shouldPrompt) { - const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?"); + const message = localize('confirm empty commit', "Are you sure you want to create an empty commit?"); const yes = localize('yes', "Yes"); const neverAgain = localize('yes never again', "Yes, Don't Show Again"); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); @@ -1859,7 +1927,7 @@ export class CommandCenter { const item = choice as CheckoutItem; try { - await item.run(repository, opts); + await item.run(opts); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) { throw err; @@ -1871,10 +1939,10 @@ export class CommandCenter { if (choice === force) { await this.cleanAll(repository); - await item.run(repository, opts); + await item.run(opts); } else if (choice === stash) { await this.stash(repository); - await item.run(repository, opts); + await item.run(opts); await this.stashPopLatest(repository); } } @@ -2003,7 +2071,9 @@ export class CommandCenter { return; } - target = choice.label; + if (choice.refName) { + target = choice.refName; + } } await repository.branch(branchName, true, target); @@ -2515,17 +2585,16 @@ export class CommandCenter { } } - if (rebase) { - await repository.syncRebase(HEAD); - } else { - await repository.sync(HEAD); - } + await repository.sync(HEAD, rebase); } @command('git.sync', { repository: true }) async sync(repository: Repository): Promise { + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const rebase = config.get('rebaseWhenSync', false) === true; + try { - await this._sync(repository, false); + await this._sync(repository, rebase); } catch (err) { if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { return; @@ -2538,13 +2607,16 @@ export class CommandCenter { @command('git._syncAll') async syncAll(): Promise { await Promise.all(this.model.repositories.map(async repository => { + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const rebase = config.get('rebaseWhenSync', false) === true; + const HEAD = repository.HEAD; if (!HEAD || !HEAD.upstream) { return; } - await repository.sync(HEAD); + await repository.sync(HEAD, rebase); })); } @@ -2970,13 +3042,7 @@ export class CommandCenter { @command('git.closeAllDiffEditors', { repository: true }) closeDiffEditors(repository: Repository): void { - const resources = [ - ...repository.indexGroup.resourceStates.map(r => r.resourceUri.fsPath), - ...repository.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath), - ...repository.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath) - ]; - - repository.closeDiffEditors(resources, resources, true); + repository.closeDiffEditors(undefined, undefined, true); } private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { @@ -3010,7 +3076,7 @@ export class CommandCenter { /* __GDPR__ "git.command" : { "owner": "lszomoru", - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command id of the command being executed" } } */ this.telemetryReporter.sendTelemetryEvent('git.command', { command: id }); diff --git a/extensions/git/src/git-editor.sh b/extensions/git/src/git-editor.sh index 1c45c2deac1..d7e0d2deece 100755 --- a/extensions/git/src/git-editor.sh +++ b/extensions/git/src/git-editor.sh @@ -1,4 +1,4 @@ #!/bin/sh ELECTRON_RUN_AS_NODE="1" \ -"$VSCODE_GIT_EDITOR_NODE" "$VSCODE_GIT_EDITOR_MAIN" $VSCODE_GIT_EDITOR_EXTRA_ARGS $@ +"$VSCODE_GIT_EDITOR_NODE" "$VSCODE_GIT_EDITOR_MAIN" $VSCODE_GIT_EDITOR_EXTRA_ARGS "$@" diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 26e1773f277..5cb944783a7 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -475,8 +475,9 @@ export class Git { const repoPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, '')); if (isWindows) { - // On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped drive path back, you get the UNC path for the mapped drive. - // So we will try to normalize it back to the mapped drive path, if possible + // On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped + // drive path back, you get the UNC path for the mapped drive. So we will try to normalize it back to the + // mapped drive path, if possible const repoUri = Uri.file(repoPath); const pathUri = Uri.file(repositoryPath); if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) { @@ -504,6 +505,13 @@ export class Git { return path.normalize(pathUri.fsPath); } + + // On Windows, there are cases in which the normalized path for a mapped folder contains a trailing `\` + // character (ex: \\server\folder\) due to the implementation of `path.normalize()`. This behaviour is + // by design as documented in https://github.com/nodejs/node/issues/1765. + if (repoUri.authority.length !== 0) { + return repoPath.replace(/\\$/, ''); + } } return repoPath; @@ -1087,7 +1095,7 @@ export class Repository { } if (!isText) { - const result = filetype(buffer); + const result = await filetype.fromBuffer(buffer); if (!result) { return { mimetype: 'application/octet-stream' }; @@ -1403,7 +1411,7 @@ export class Repository { if (message) { options.input = message; - args.push('--file', '-'); + args.push('--allow-empty-message', '--file', '-'); } if (opts.verbose) { @@ -1467,7 +1475,7 @@ export class Repository { const args = ['rebase', '--continue']; try { - await this.exec(args); + await this.exec(args, { env: { GIT_EDITOR: 'true' } }); } catch (commitErr) { await this.handleCommitError(commitErr); } @@ -1785,12 +1793,12 @@ export class Repository { } catch (err) { if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.PushRejected; + } else if (/Permission.*denied/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.PermissionDenied; } else if (/Could not read from remote repository/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; } else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.NoUpstreamBranch; - } else if (/Permission.*denied/.test(err.stderr || '')) { - err.gitErrorCode = GitErrorCodes.PermissionDenied; } throw err; diff --git a/extensions/git/src/gitEditor.ts b/extensions/git/src/gitEditor.ts index 0a2bb752afe..fb0688e4401 100644 --- a/extensions/git/src/gitEditor.ts +++ b/extensions/git/src/gitEditor.ts @@ -5,20 +5,29 @@ import * as path from 'path'; import { TabInputText, Uri, window, workspace } from 'vscode'; import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; +import { ITerminalEnvironmentProvider } from './terminal'; import { EmptyDisposable, IDisposable } from './util'; interface GitEditorRequest { commitMessagePath?: string; } -export class GitEditor implements IIPCHandler { +export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { + private env: { [key: string]: string }; private disposable: IDisposable = EmptyDisposable; - constructor(private ipc?: IIPCServer) { + constructor(ipc?: IIPCServer) { if (ipc) { this.disposable = ipc.registerHandler('git-editor', this); } + + this.env = { + GIT_EDITOR: `"${path.join(__dirname, ipc ? 'git-editor.sh' : 'git-editor-empty.sh')}"`, + VSCODE_GIT_EDITOR_NODE: process.execPath, + VSCODE_GIT_EDITOR_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', + VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'git-editor-main.js') + }; } async handle({ commitMessagePath }: GitEditorRequest): Promise { @@ -39,24 +48,13 @@ export class GitEditor implements IIPCHandler { } getEnv(): { [key: string]: string } { - if (!this.ipc) { - return { - GIT_EDITOR: `"${path.join(__dirname, 'git-editor-empty.sh')}"` - }; - } - - const env: { [key: string]: string } = { - VSCODE_GIT_EDITOR_NODE: process.execPath, - VSCODE_GIT_EDITOR_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', - VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'git-editor-main.js') - }; - const config = workspace.getConfiguration('git'); - if (config.get('useEditorAsCommitInput')) { - env.GIT_EDITOR = `"${path.join(__dirname, 'git-editor.sh')}"`; - } + return config.get('useEditorAsCommitInput') ? this.env : {}; + } - return env; + getTerminalEnv(): { [key: string]: string } { + const config = workspace.getConfiguration('git'); + return config.get('useEditorAsCommitInput') && config.get('terminalGitEditor') ? this.env : {}; } dispose(): void { diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index ad4583cbfe1..c9de357b1a6 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vscode'; +import { ITerminalEnvironmentProvider } from '../terminal'; import { toDisposable } from '../util'; import * as path from 'path'; import * as http from 'http'; @@ -27,7 +28,7 @@ export interface IIPCHandler { handle(request: any): Promise; } -export async function createIPCServer(context?: string): Promise { +export async function createIPCServer(context?: string): Promise { const server = http.createServer(); const hash = crypto.createHash('sha1'); @@ -65,7 +66,7 @@ export interface IIPCServer extends Disposable { registerHandler(name: string, handler: IIPCHandler): Disposable; } -class IPCServer implements IIPCServer, Disposable { +export class IPCServer implements IIPCServer, ITerminalEnvironmentProvider, Disposable { private handlers = new Map(); get ipcHandlePath(): string { return this._ipcHandlePath; } @@ -110,6 +111,10 @@ class IPCServer implements IIPCServer, Disposable { return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath }; } + getTerminalEnv(): { [key: string]: string } { + return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath }; + } + dispose(): void { this.handlers.clear(); this.server.close(); diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 46f612539fb..40147d6c0e6 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,8 +25,9 @@ import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; import { OutputChannelLogger } from './log'; -import { createIPCServer, IIPCServer } from './ipc/ipcServer'; +import { createIPCServer, IPCServer } from './ipc/ipcServer'; import { GitEditor } from './gitEditor'; +import { GitPostCommitCommandsProvider } from './postCommitCommands'; const deactivateTasks: { (): Promise }[] = []; @@ -62,22 +63,22 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu return !skip; }); - let ipc: IIPCServer | undefined = undefined; + let ipcServer: IPCServer | undefined = undefined; try { - ipc = await createIPCServer(context.storagePath); + ipcServer = await createIPCServer(context.storagePath); } catch (err) { outputChannelLogger.logError(`Failed to create git IPC: ${err}`); } - const askpass = new Askpass(ipc); + const askpass = new Askpass(ipcServer); disposables.push(askpass); - const gitEditor = new GitEditor(ipc); + const gitEditor = new GitEditor(ipcServer); disposables.push(gitEditor); - const environment = { ...askpass.getEnv(), ...gitEditor.getEnv() }; - const terminalEnvironmentManager = new TerminalEnvironmentManager(context, environment); + const environment = { ...askpass.getEnv(), ...gitEditor.getEnv(), ...ipcServer?.getEnv() }; + const terminalEnvironmentManager = new TerminalEnvironmentManager(context, [askpass, gitEditor, ipcServer]); disposables.push(terminalEnvironmentManager); outputChannelLogger.logInfo(localize('using git', "Using git {0} from {1}", info.version, info.path)); @@ -117,6 +118,9 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu new GitTimelineProvider(model, cc) ); + const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); + model.registerPostCommitCommandsProvider(postCommitCommandsProvider); + checkGitVersion(info); return model; diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 87c510b3b03..5fc1b4d06ac 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -13,12 +13,13 @@ import * as path from 'path'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; import { fromGitUri } from './uri'; -import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher } from './api/git'; +import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider } from './api/git'; import { Askpass } from './askpass'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { OutputChannelLogger } from './log'; +import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; const localize = nls.loadMessageBundle(); @@ -50,7 +51,7 @@ interface OpenRepository extends Disposable { repository: Repository; } -export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerRegistry { +export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { private _onDidOpenRepository = new EventEmitter(); readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; @@ -105,6 +106,11 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR private _onDidRemoveRemoteSourcePublisher = new EventEmitter(); readonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event; + private postCommitCommandsProviders = new Set(); + + private _onDidChangePostCommitCommandsProviders = new EventEmitter(); + readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event; + private showRepoOnHomeDriveRootWarning = true; private pushErrorHandlers = new Set(); @@ -369,7 +375,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR } const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); - const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this.globalState, this.outputChannelLogger, this.telemetryReporter); + const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this, this.globalState, this.outputChannelLogger, this.telemetryReporter); this.open(repository); repository.status(); // do not await this, we want SCM to know about the repo asap @@ -506,6 +512,10 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR return this.openRepositories.filter(r => r.repository === hint)[0]; } + if (hint instanceof ApiRepository) { + return this.openRepositories.filter(r => r.repository === hint.repository)[0]; + } + if (typeof hint === 'string') { hint = Uri.file(hint); } @@ -582,6 +592,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR return [...this.remoteSourcePublishers.values()]; } + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { + this.postCommitCommandsProviders.add(provider); + this._onDidChangePostCommitCommandsProviders.fire(); + + return toDisposable(() => { + this.postCommitCommandsProviders.delete(provider); + this._onDidChangePostCommitCommandsProviders.fire(); + }); + } + + getPostCommitCommandsProviders(): PostCommitCommandsProvider[] { + return [...this.postCommitCommandsProviders.values()]; + } + registerCredentialsProvider(provider: CredentialsProvider): Disposable { return this.askpass.registerCredentialsProvider(provider); } diff --git a/extensions/git/src/postCommitCommands.ts b/extensions/git/src/postCommitCommands.ts new file mode 100644 index 00000000000..85d1689011a --- /dev/null +++ b/extensions/git/src/postCommitCommands.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vscode-nls'; +import { Command, Disposable, Event } from 'vscode'; +import { PostCommitCommandsProvider } from './api/git'; + +export interface IPostCommitCommandsProviderRegistry { + readonly onDidChangePostCommitCommandsProviders: Event; + + getPostCommitCommandsProviders(): PostCommitCommandsProvider[]; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; +} + +const localize = nls.loadMessageBundle(); + +export class GitPostCommitCommandsProvider implements PostCommitCommandsProvider { + getCommands(): Command[] { + return [ + { + command: 'git.push', + title: localize('scm secondary button commit and push', "Commit & Push") + }, + { + command: 'git.sync', + title: localize('scm secondary button commit and sync', "Commit & Sync") + }, + ]; + } +} diff --git a/extensions/git/src/protocolHandler.ts b/extensions/git/src/protocolHandler.ts index c0c3f2a6527..33decfe1846 100644 --- a/extensions/git/src/protocolHandler.ts +++ b/extensions/git/src/protocolHandler.ts @@ -38,7 +38,15 @@ export class GitProtocolHandler implements UriHandler { let cloneUri: Uri; try { - cloneUri = Uri.parse(Array.isArray(data.url) ? data.url[0] : data.url, true); + let rawUri = Array.isArray(data.url) ? data.url[0] : data.url; + + // Handle SSH Uri + // Ex: git@github.com:microsoft/vscode.git + rawUri = rawUri.replace(/^(git@[^\/:]+)(:)/i, 'ssh://$1/'); + + cloneUri = Uri.parse(rawUri, true); + + // Validate against supported schemes if (!schemes.has(cloneUri.scheme.toLowerCase())) { throw new Error('Unsupported scheme.'); } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 7e34385933e..9dc29297506 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,6 +5,7 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as picomatch from 'picomatch'; import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import * as nls from 'vscode-nls'; @@ -21,6 +22,7 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; +import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -615,9 +617,9 @@ class ResourceCommandResolver { if (!resource.leftUri) { const bothModified = resource.type === Status.BOTH_MODIFIED; - if (resource.rightUri && bothModified && workspace.getConfiguration('git').get('experimental.mergeEditor', false)) { + if (resource.rightUri && workspace.getConfiguration('git').get('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) { return { - command: '_git.openMergeEditor', + command: 'git.openMergeEditor', title: localize('open.merge', "Open Merge"), arguments: [resource.rightUri] }; @@ -867,6 +869,7 @@ export class Repository implements Disposable { private isRepositoryHuge: false | { limit: number } = false; private didWarnAboutLimit = false; + private isBranchProtectedMatcher: picomatch.Matcher | undefined; private resourceCommandResolver = new ResourceCommandResolver(this); private disposables: Disposable[] = []; @@ -874,6 +877,7 @@ export class Repository implements Disposable { private readonly repository: BaseRepository, private pushErrorHandlerRegistry: IPushErrorHandlerRegistry, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry, + postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry, globalState: Memento, outputChannelLogger: OutputChannelLogger, private telemetryReporter: TelemetryReporter @@ -933,18 +937,19 @@ export class Repository implements Disposable { updateIndexGroupVisibility(); workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('git.experimental.mergeEditor')) { - this.mergeGroup.resourceStates = this.mergeGroup.resourceStates.map(r => r.clone()); + if (e.affectsConfiguration('git.mergeEditor')) { + // this.mergeGroup.resourceStates = this.mergeGroup.resourceStates.map(r => r.clone()); + this.status(); } }, undefined, this.disposables); filterEvent(workspace.onDidChangeConfiguration, e => - e.affectsConfiguration('git.branchSortOrder', root) + e.affectsConfiguration('git.branchProtection', root) + || e.affectsConfiguration('git.branchSortOrder', root) || e.affectsConfiguration('git.untrackedChanges', root) || e.affectsConfiguration('git.ignoreSubmodules', root) || e.affectsConfiguration('git.openDiffOnClick', root) - || e.affectsConfiguration('git.rebaseWhenSync', root) - || e.affectsConfiguration('git.showUnpublishedCommitsButton', root) + || e.affectsConfiguration('git.showActionButton', root) )(this.updateModelState, this, this.disposables); const updateInputBoxVisibility = () => { @@ -985,12 +990,16 @@ export class Repository implements Disposable { } }, null, this.disposables); + const onDidChangeBranchProtection = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', root)); + onDidChangeBranchProtection(this.updateBranchProtectionMatcher, this, this.disposables); + this.updateBranchProtectionMatcher(); + const statusBar = new StatusBarCommands(this, remoteSourcePublisherRegistry); this.disposables.push(statusBar); statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables); this._sourceControl.statusBarCommands = statusBar.commands; - const actionButton = new ActionButtonCommand(this); + const actionButton = new ActionButtonCommand(this, postCommitCommandsProviderRegistry); this.disposables.push(actionButton); actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button); this._sourceControl.actionButton = actionButton.button; @@ -1289,7 +1298,7 @@ export class Repository implements Disposable { }); } - closeDiffEditors(indexResources: string[], workingTreeResources: string[], ignoreSetting: boolean = false): void { + closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting: boolean = false): void { const config = workspace.getConfiguration('git', Uri.file(this.root)); if (!config.get('closeDiffOnOperation', false) && !ignoreSetting) { return; } @@ -1298,11 +1307,11 @@ export class Repository implements Disposable { for (const tab of window.tabGroups.all.map(g => g.tabs).flat()) { const { input } = tab; if (input instanceof TabInputTextDiff || input instanceof TabInputNotebookDiff) { - if (input.modified.scheme === 'git' && indexResources.some(r => pathEquals(r, input.modified.fsPath))) { + if (input.modified.scheme === 'git' && (indexResources === undefined || indexResources.some(r => pathEquals(r, input.modified.fsPath)))) { // Index diffEditorTabsToClose.push(tab); } - if (input.modified.scheme === 'file' && input.original.scheme === 'git' && workingTreeResources.some(r => pathEquals(r, input.modified.fsPath))) { + if (input.modified.scheme === 'file' && input.original.scheme === 'git' && (workingTreeResources === undefined || workingTreeResources.some(r => pathEquals(r, input.modified.fsPath)))) { // Working Tree diffEditorTabsToClose.push(tab); } @@ -1501,13 +1510,8 @@ export class Repository implements Disposable { } @throttle - sync(head: Branch): Promise { - return this._sync(head, false); - } - - @throttle - async syncRebase(head: Branch): Promise { - return this._sync(head, true); + sync(head: Branch, rebase: boolean): Promise { + return this._sync(head, rebase); } private async _sync(head: Branch, rebase: boolean): Promise { @@ -1891,9 +1895,9 @@ export class Repository implements Disposable { /* __GDPR__ "statusLimit" : { "owner": "lszomoru", - "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting indicating whether submodules are ignored" }, + "limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Setting indicating the limit of status entries" }, + "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" } } */ this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength }); @@ -2025,6 +2029,9 @@ export class Repository implements Disposable { // set count badge this.setCountBadge(); + // set mergeChanges context + commands.executeCommand('setContext', 'git.mergeChanges', merge.map(item => item.resourceUri.toString())); + this._onDidChangeStatus.fire(); this._sourceControl.commitTemplate = await this.getInputTemplate(); @@ -2211,6 +2218,21 @@ export class Repository implements Disposable { } } + private updateBranchProtectionMatcher(): void { + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const branchProtectionGlobs = scopedConfig.get('branchProtection')!.map(bp => bp.trim()).filter(bp => bp !== ''); + + if (branchProtectionGlobs.length === 0) { + this.isBranchProtectedMatcher = undefined; + } else { + this.isBranchProtectedMatcher = picomatch(branchProtectionGlobs); + } + } + + public isBranchProtected(name: string = this.HEAD?.name ?? ''): boolean { + return this.isBranchProtectedMatcher ? this.isBranchProtectedMatcher(name) : false; + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index 7cf06e0ed4f..bfb61d4266c 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -24,7 +24,8 @@ class CheckoutStatusBar { get command(): Command | undefined { const rebasing = !!this.repository.rebaseCommit; - const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`; + const isBranchProtected = this.repository.isBranchProtected(); + const title = `${isBranchProtected ? '$(lock)' : '$(git-branch)'} ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`; return { command: 'git.checkout', @@ -144,10 +145,7 @@ class SyncStatusBar { text += this.repository.syncLabel; } - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const rebaseWhenSync = config.get('rebaseWhenSync'); - - command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync'; + command = 'git.sync'; tooltip = this.repository.syncTooltip; } else { icon = '$(cloud-upload)'; diff --git a/extensions/git/src/terminal.ts b/extensions/git/src/terminal.ts index 2eda93151d2..9501cc88bba 100644 --- a/extensions/git/src/terminal.ts +++ b/extensions/git/src/terminal.ts @@ -6,27 +6,15 @@ import { ExtensionContext, workspace } from 'vscode'; import { filterEvent, IDisposable } from './util'; +export interface ITerminalEnvironmentProvider { + getTerminalEnv(): { [key: string]: string }; +} + export class TerminalEnvironmentManager { private readonly disposable: IDisposable; - private _enabled = false; - private set enabled(enabled: boolean) { - if (this._enabled === enabled) { - return; - } - - this._enabled = enabled; - this.context.environmentVariableCollection.clear(); - - if (enabled) { - for (const name of Object.keys(this.env)) { - this.context.environmentVariableCollection.replace(name, this.env[name]); - } - } - } - - constructor(private readonly context: ExtensionContext, private readonly env: { [key: string]: string }) { + constructor(private readonly context: ExtensionContext, private readonly envProviders: (ITerminalEnvironmentProvider | undefined)[]) { this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git')) (this.refresh, this); @@ -35,7 +23,19 @@ export class TerminalEnvironmentManager { private refresh(): void { const config = workspace.getConfiguration('git', null); - this.enabled = config.get('enabled', true) && config.get('terminalAuthentication', true); + this.context.environmentVariableCollection.clear(); + + if (!config.get('enabled', true)) { + return; + } + + for (const envProvider of this.envProviders) { + const terminalEnv = envProvider?.getTerminalEnv() ?? {}; + + for (const name of Object.keys(terminalEnv)) { + this.context.environmentVariableCollection.replace(name, terminalEnv[name]); + } + } } dispose(): void { diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 53ab6c9e310..e36cebf97cf 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -51,9 +51,7 @@ export function anyEvent(...events: Event[]): Event { return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i)))); - if (disposables) { - disposables.push(result); - } + disposables?.push(result); return result; }; diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 1f1c02d3356..c62c25401f2 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -12,10 +12,9 @@ "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", - "../../src/vscode-dts/vscode.proposed.scmInput.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", - "../../src/vscode-dts/vscode.proposed.tabs.d.ts", + "../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts", "../../src/vscode-dts/vscode.proposed.timeline.d.ts", "../types/lib.textEncoder.d.ts" ] diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 60b91dbb320..502b35d9c38 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -7,6 +7,47 @@ resolved "https://registry.yarnpkg.com/@joaomoreno/unique-names-generator/-/unique-names-generator-5.0.0.tgz#a67fe66e3d825c929fc97abfdf17fd80a72beab0" integrity sha512-3kP6z7aoGEoM3tvhTBZioYa1QFkovOU8uxAlVclnZlXivwF/WTE5EcOzvoDdM+jtjJyWvCMDR1Q4RBjDqupD3A== +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@types/byline@4.2.31": version "4.2.31" resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.31.tgz#0e61fcb9c03e047d21c4496554c7116297ab60cd" @@ -14,13 +55,6 @@ dependencies: "@types/node" "*" -"@types/file-type@^5.2.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@types/file-type/-/file-type-5.2.1.tgz#e7af49e08187b6b7598509c5e416669d25fa3461" - integrity sha512-Im0cJaIPJbbpuW91OrjXnqWPZCJK/tcFy2cFX+1qjG1gubgVZPPO9OVsTVAjotN4I1E6FAV0eIqt+rR8Y1c3iA== - dependencies: - "@types/node" "*" - "@types/mocha@^9.1.1": version "9.1.1" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" @@ -46,10 +80,13 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" "@vscode/iconv-lite-umd@0.7.0": version "0.7.0" @@ -61,10 +98,24 @@ byline@^5.0.0: resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= -file-type@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74" - integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q= +file-type@16.5.4: + version "16.5.4" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== isexe@^2.0.0: version "2.0.0" @@ -76,15 +127,69 @@ jschardet@3.0.0: resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +peek-readable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== + picomatch@2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== + dependencies: + readable-stream "^3.6.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strtok3@^6.2.4: + version "6.3.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.1.0" + +token-types@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.0.tgz#b66bc3d67420c6873222a424eee64a744f4c2f13" + integrity sha512-P0rrp4wUpefLncNamWIef62J0v0kQR/GfDVji9WKY7GDCWy5YbVSrKUTam07iWPZQGy0zWNOfstYTykMmPNR7w== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-uri@^2.0.0: version "2.0.0" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 5ef5626aa14..d9ccdc42d4c 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -48,7 +48,7 @@ } } }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", "scripts": { @@ -61,8 +61,8 @@ "dependencies": { "node-fetch": "2.6.7", "uuid": "8.1.0", - "@vscode/extension-telemetry": "0.4.10", - "vscode-nls": "^5.0.0", + "@vscode/extension-telemetry": "0.6.2", + "vscode-nls": "^5.1.0", "vscode-tas-client": "^0.1.47" }, "devDependencies": { diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 6816d9d992e..1614794fcb0 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -243,7 +243,7 @@ export class GitHubServer implements IGitHubServer { try { return await Promise.race([ codeExchangePromise.promise, - new Promise((_, reject) => setTimeout(() => reject('Cancelled'), 60000)), + new Promise((_, reject) => setTimeout(() => reject('Timed out'), 300_000)), // 5min timeout promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise ]); } finally { @@ -276,7 +276,7 @@ export class GitHubServer implements IGitHubServer { vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${port}/signin?nonce=${encodeURIComponent(server.nonce)}`)); const { code } = await Promise.race([ server.waitForOAuthResponse(), - new Promise((_, reject) => setTimeout(() => reject('Cancelled'), 60000)), + new Promise((_, reject) => setTimeout(() => reject('Timed out'), 300_000)), // 5min timeout promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise ]); codeToExchange = code; diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index 12caf60ff8d..6ebb79992dc 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -25,10 +61,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" asynckit@^0.4.0: version "0.4.0" @@ -104,10 +143,10 @@ uuid@8.1.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-tas-client@^0.1.47: version "0.1.47" diff --git a/extensions/github/package.json b/extensions/github/package.json index af7b544f596..3ddff7fb774 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -25,11 +25,34 @@ "supported": true } }, + "enabledApiProposals": [ + "contribShareMenu", + "contribEditSessions" + ], "contributes": { "commands": [ { "command": "github.publish", "title": "Publish to GitHub" + }, + { + "command": "github.copyVscodeDevLink", + "title": "Copy vscode.dev Link" + }, + { + "command": "github.copyVscodeDevLinkFile", + "title": "Copy vscode.dev Link" + }, + { + "command": "github.openOnVscodeDev", + "title": "Open on vscode.dev", + "icon": "$(globe)" + } + ], + "continueEditSession": [ + { + "command": "github.openOnVscodeDev", + "when": "github.hasGitHubRepo" } ], "menus": { @@ -37,6 +60,30 @@ { "command": "github.publish", "when": "git-base.gitEnabled" + }, + { + "command": "github.copyVscodeDevLink", + "when": "false" + }, + { + "command": "github.copyVscodeDevLinkFile", + "when": "false" + }, + { + "command": "github.openOnVscodeDev", + "when": "false" + } + ], + "file/share": [ + { + "command": "github.copyVscodeDevLinkFile", + "when": "github.hasGitHubRepo" + } + ], + "editor/context/share": [ + { + "command": "github.copyVscodeDevLink", + "when": "github.hasGitHubRepo && resourceScheme != untitled" } ] }, @@ -86,7 +133,7 @@ "dependencies": { "@octokit/rest": "^18.0.1", "tunnel": "^0.0.6", - "vscode-nls": "^4.1.2" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index b43271a87a0..4e9bdab9dfa 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -6,6 +6,8 @@ "welcome.publishFolder": { "message": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ + "{Locked='$(github)'}", + "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" @@ -14,6 +16,8 @@ "welcome.publishWorkspaceFolder": { "message": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ + "{Locked='$(github)'}", + "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 42ed8198287..af3484580fd 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -7,6 +7,32 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; import { publishRepository } from './publish'; import { DisposableStore } from './util'; +import { getPermalink } from './links'; + +function getVscodeDevHost(): string { + return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; +} + +async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { + try { + const permalink = getPermalink(gitAPI, useSelection, getVscodeDevHost()); + if (permalink) { + return vscode.env.clipboard.writeText(permalink); + } + } catch (err) { + vscode.window.showErrorMessage(err.message); + } +} + +async function openVscodeDevLink(gitAPI: GitAPI): Promise { + try { + const permalink = getPermalink(gitAPI, true, getVscodeDevHost()); + return permalink ? vscode.Uri.parse(permalink) : undefined; + } catch (err) { + vscode.window.showErrorMessage(err.message); + return undefined; + } +} export function registerCommands(gitAPI: GitAPI): vscode.Disposable { const disposables = new DisposableStore(); @@ -19,5 +45,17 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable { } })); + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => { + return copyVscodeDevLink(gitAPI, true); + })); + + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => { + return copyVscodeDevLink(gitAPI, false); + })); + + disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => { + return openVscodeDevLink(gitAPI); + })); + return disposables; } diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index 2fbe1d597fd..a3a84b033dd 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -5,10 +5,10 @@ import { commands, Disposable, ExtensionContext, extensions } from 'vscode'; import { GithubRemoteSourceProvider } from './remoteSourceProvider'; -import { GitExtension } from './typings/git'; +import { API, GitExtension } from './typings/git'; import { registerCommands } from './commands'; import { GithubCredentialProviderManager } from './credentialProvider'; -import { DisposableStore } from './util'; +import { DisposableStore, repositoryHasGitHubRemote } from './util'; import { GithubPushErrorHandler } from './pushErrorHandler'; import { GitBaseExtension } from './typings/git-base'; import { GithubRemoteSourcePublisher } from './remoteSourcePublisher'; @@ -48,6 +48,21 @@ function initializeGitBaseExtension(): Disposable { return disposables; } +function setGitHubContext(gitAPI: API, disposables: DisposableStore) { + if (gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) { + commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + } else { + const openRepoDisposable = gitAPI.onDidOpenRepository(async e => { + await e.status(); + if (repositoryHasGitHubRemote(e)) { + commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + openRepoDisposable.dispose(); + } + }); + disposables.add(openRepoDisposable); + } +} + function initializeGitExtension(): Disposable { const disposables = new DisposableStore(); @@ -64,6 +79,7 @@ function initializeGitExtension(): Disposable { disposables.add(new GithubCredentialProviderManager(gitAPI)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler())); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); + setGitHubContext(gitAPI, disposables); commands.executeCommand('setContext', 'git-base.gitEnabled', true); } else { diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts new file mode 100644 index 00000000000..66f5b071ecd --- /dev/null +++ b/extensions/github/src/links.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { API as GitAPI, Repository } from './typings/git'; +import { getRepositoryFromUrl } from './util'; + +export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean { + return file.path.toLowerCase() === repository.rootUri.path.toLowerCase() || + (file.path.toLowerCase().startsWith(repository.rootUri.path.toLowerCase()) && + file.path.substring(repository.rootUri.path.length).startsWith('/')); +} + +export function getRepositoryForFile(gitAPI: GitAPI, file: vscode.Uri): Repository | undefined { + for (const repository of gitAPI.repositories) { + if (isFileInRepo(repository, file)) { + return repository; + } + } + return undefined; +} + +enum LinkType { + File = 1, + Notebook = 2 +} + +interface IFilePosition { + type: LinkType.File; + uri: vscode.Uri; + range: vscode.Range | undefined; +} + +interface INotebookPosition { + type: LinkType.Notebook; + uri: vscode.Uri; + cellIndex: number; + range: vscode.Range | undefined; +} + +function getFileAndPosition(): IFilePosition | INotebookPosition | undefined { + let uri: vscode.Uri | undefined; + let range: vscode.Range | undefined; + if (vscode.window.activeTextEditor) { + uri = vscode.window.activeTextEditor.document.uri; + + if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) { + // if the active editor is a notebook editor and the focus is inside any a cell text editor + // generate deep link for text selection for the notebook cell. + const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment); + const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start; + const range = cell !== undefined ? vscode.window.activeTextEditor.selection : undefined; + return { type: LinkType.Notebook, uri, cellIndex, range }; + } else { + // the active editor is a text editor + range = vscode.window.activeTextEditor.selection; + return { type: LinkType.File, uri, range }; + } + } + + if (vscode.window.activeNotebookEditor) { + // if the active editor is a notebook editor but the focus is not inside any cell text editor, generate deep link for the cell selection in the notebook document. + return { type: LinkType.Notebook, uri: vscode.window.activeNotebookEditor.notebook.uri, cellIndex: vscode.window.activeNotebookEditor.selection.start, range: undefined }; + } + + return undefined; +} + +function rangeString(range: vscode.Range | undefined) { + if (!range) { + return ''; + } + let hash = `#L${range.start.line + 1}`; + if (range.start.line !== range.end.line) { + hash += `-L${range.end.line + 1}`; + } + return hash; +} + +export function notebookCellRangeString(index: number | undefined, range: vscode.Range | undefined) { + if (index === undefined) { + return ''; + } + + if (!range) { + return `#C${index + 1}`; + } + + let hash = `#C${index + 1}:L${range.start.line + 1}`; + if (range.start.line !== range.end.line) { + hash += `-L${range.end.line + 1}`; + } + return hash; +} + +export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined { + hostPrefix = hostPrefix ?? 'https://github.com'; + const fileAndPosition = getFileAndPosition(); + if (!fileAndPosition) { + return; + } + const uri = fileAndPosition.uri; + + // Use the first repo if we cannot determine a repo from the uri. + const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0]; + if (!gitRepo) { + return; + } + let repo: { owner: string; repo: string } | undefined; + gitRepo.state.remotes.find(remote => { + if (remote.fetchUrl) { + const foundRepo = getRepositoryFromUrl(remote.fetchUrl); + if (foundRepo && (remote.name === gitRepo.state.HEAD?.upstream?.remote)) { + repo = foundRepo; + return; + } else if (foundRepo && !repo) { + repo = foundRepo; + } + } + return; + }); + if (!repo) { + return; + } + + const commitHash = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${gitRepo.state.HEAD?.commit}` : ''; + const fileSegments = fileAndPosition.type === LinkType.File + ? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '') + : (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : ''); + + return `${hostPrefix}/${repo.owner}/${repo.repo}${commitHash + }${fileSegments}`; +} diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index e365a7172b7..e8eeb851549 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -7,17 +7,7 @@ import { workspace } from 'vscode'; import { RemoteSourceProvider, RemoteSource } from './typings/git-base'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; - -function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined { - const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url) - || /^git@github\.com:([^/]+)\/([^/]+)\.git/i.exec(url); - return match ? { owner: match[1], repo: match[2] } : undefined; -} - -function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined { - const match = /^([^/]+)\/([^/]+)$/i.exec(query); - return match ? { owner: match[1], repo: match[2] } : undefined; -} +import { getRepositoryFromQuery, getRepositoryFromUrl } from './util'; function asRemoteSource(raw: any): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); diff --git a/extensions/github/src/util.ts b/extensions/github/src/util.ts index c7d23a82301..3d8bf4a40be 100644 --- a/extensions/github/src/util.ts +++ b/extensions/github/src/util.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { Repository } from './typings/git'; export class DisposableStore { @@ -21,3 +22,18 @@ export class DisposableStore { this.disposables.clear(); } } + +export function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined { + const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/i.exec(url) + || /^git@github\.com:([^/]+)\/([^/]+?)(\.git)?$/i.exec(url); + return match ? { owner: match[1], repo: match[2] } : undefined; +} + +export function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined { + const match = /^([^/]+)\/([^/]+)$/i.exec(query); + return match ? { owner: match[1], repo: match[2] } : undefined; +} + +export function repositoryHasGitHubRemote(repository: Repository) { + return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined); +} diff --git a/extensions/github/yarn.lock b/extensions/github/yarn.lock index 9933736b08e..5465f9ba25a 100644 --- a/extensions/github/yarn.lock +++ b/extensions/github/yarn.lock @@ -153,10 +153,10 @@ universal-user-agent@^6.0.0: resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -vscode-nls@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" - integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== webidl-conversions@^3.0.0: version "3.0.1" diff --git a/extensions/grunt/package.json b/extensions/grunt/package.json index 8dd9eced530..245fd717f8c 100644 --- a/extensions/grunt/package.json +++ b/extensions/grunt/package.json @@ -17,7 +17,7 @@ "watch": "gulp watch-extension:grunt" }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/grunt/yarn.lock b/extensions/grunt/yarn.lock index 22c406bc73f..90475ddc7e0 100644 --- a/extensions/grunt/yarn.lock +++ b/extensions/grunt/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/gulp/package.json b/extensions/gulp/package.json index 8352957083e..062dc4a788f 100644 --- a/extensions/gulp/package.json +++ b/extensions/gulp/package.json @@ -17,7 +17,7 @@ "watch": "gulp watch-extension:gulp" }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/gulp/yarn.lock b/extensions/gulp/yarn.lock index 22c406bc73f..90475ddc7e0 100644 --- a/extensions/gulp/yarn.lock +++ b/extensions/gulp/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/handlebars/package.json b/extensions/handlebars/package.json index 0eb6cb2eb2b..88976cf36ff 100644 --- a/extensions/handlebars/package.json +++ b/extensions/handlebars/package.json @@ -36,6 +36,12 @@ "scopeName": "text.html.handlebars", "path": "./syntaxes/Handlebars.tmLanguage.json" } + ], + "htmlLanguageParticipants": [ + { + "languageId": "handlebars", + "autoInsert": true + } ] }, "repository": { diff --git a/extensions/html-language-features/client/src/autoInsertion.ts b/extensions/html-language-features/client/src/autoInsertion.ts index 170afa46c02..e95e6a64a09 100644 --- a/extensions/html-language-features/client/src/autoInsertion.ts +++ b/extensions/html-language-features/client/src/autoInsertion.ts @@ -5,8 +5,9 @@ import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason, TextDocumentContentChangeEvent } from 'vscode'; import { Runtime } from './htmlClient'; +import { LanguageParticipants } from './languageParticipants'; -export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable, supportedLanguages: { [id: string]: boolean }, runtime: Runtime): Disposable { +export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable, languageParticipants: LanguageParticipants, runtime: Runtime): Disposable { const disposables: Disposable[] = []; workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables); @@ -33,7 +34,7 @@ export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose' return; } const document = editor.document; - if (!supportedLanguages[document.languageId]) { + if (!languageParticipants.useAutoInsert(document.languageId)) { return; } const configurations = workspace.getConfiguration(undefined, document.uri); diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts index ab23520fe79..fb8cc9071a8 100644 --- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, ExtensionContext, Uri } from 'vscode'; -import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; -import { startClient, LanguageClientConstructor } from '../htmlClient'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; declare const Worker: { @@ -15,7 +15,7 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -42,7 +42,7 @@ export async function activate(context: ExtensionContext) { export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } } diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 80e5f2f04d9..71d847a4f39 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -125,7 +125,7 @@ function collectInWorkspaces(workspaceUris: Set): Set { } function collectInExtensions(localExtensionUris: Set, externalUris: Set): void { - for (const extension of extensions.all) { + for (const extension of extensions.allAcrossExtensionHosts) { const customData = extension.packageJSON?.contributes?.html?.customData; if (Array.isArray(customData)) { for (const uriOrPath of customData) { diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 60accb3870b..d5cf527713f 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -9,7 +9,7 @@ const localize = nls.loadMessageBundle(); import { languages, ExtensionContext, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, extensions, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList, SemanticTokensLegend, - DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands + DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands, OutputChannel } from 'vscode'; import { LanguageClientOptions, RequestType, DocumentRangeFormattingParams, @@ -18,6 +18,7 @@ import { import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; import { activateAutoInsertion } from './autoInsertion'; +import { getLanguageParticipants, LanguageParticipants } from './languageParticipants'; namespace CustomDataChangedNotification { export const type: NotificationType = new NotificationType('html/customDataChanged'); @@ -74,6 +75,8 @@ export interface TelemetryReporter { export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; +export const languageServerDescription = localize('htmlserver.name', 'HTML Language Server'); + export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; fileFs?: FileSystemProvider; @@ -83,11 +86,69 @@ export interface Runtime { }; } -export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { +export interface AsyncDisposable { + dispose(): Promise; +} - const toDispose = context.subscriptions; +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - const documentSelector = ['html', 'handlebars']; + const outputChannel = window.createOutputChannel(languageServerDescription); + + const languageParticipants = getLanguageParticipants(); + context.subscriptions.push(languageParticipants); + + let client: Disposable | undefined = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime); + + const promptForLinkedEditingKey = 'html.promptForLinkedEditing'; + if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) { + const config = workspace.getConfiguration('editor', { languageId: 'html' }); + if (!config.get('linkedEditing') && !config.get('renameOnType')) { + const activeEditorListener = window.onDidChangeActiveTextEditor(async e => { + if (e && languageParticipants.hasLanguage(e.document.languageId)) { + context.globalState.update(promptForLinkedEditingKey, false); + activeEditorListener.dispose(); + const configure = localize('configureButton', 'Configure'); + const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); + if (res === configure) { + commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); + } + } + }); + context.subscriptions.push(activeEditorListener); + } + } + + let restartTrigger: Disposable | undefined; + languageParticipants.onDidChange(() => { + if (restartTrigger) { + restartTrigger.dispose(); + } + restartTrigger = runtime.timer.setTimeout(async () => { + if (client) { + outputChannel.appendLine('Extensions have changed, restarting HTML server...'); + outputChannel.appendLine(''); + const oldClient = client; + client = undefined; + await oldClient.dispose(); + client = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime); + } + }, 2000); + }); + + return { + dispose: async () => { + restartTrigger?.dispose(); + await client?.dispose(); + outputChannel.dispose(); + } + }; +} + +async function startClientWithParticipants(languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, outputChannel: OutputChannel, runtime: Runtime): Promise { + + const toDispose: Disposable[] = []; + + const documentSelector = languageParticipants.documentSelector; const embeddedLanguages = { css: true, javascript: true }; let rangeFormatting: Disposable | undefined = undefined; @@ -129,22 +190,23 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } } }; + clientOptions.outputChannel = outputChannel; // Create the language client and start the client. - const client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions); + const client = newLanguageClient('html', languageServerDescription, clientOptions); client.registerProposedFeatures(); await client.start(); toDispose.push(serveFileSystemRequests(client, runtime)); - const customDataSource = getCustomDataSource(runtime, context.subscriptions); + const customDataSource = getCustomDataSource(runtime, toDispose); client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - client.onRequest(CustomDataContent.type, customDataSource.getContent); + }, undefined, toDispose); + toDispose.push(client.onRequest(CustomDataContent.type, customDataSource.getContent)); const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { @@ -155,7 +217,8 @@ export async function startClient(context: ExtensionContext, newLanguageClient: }; return client.sendRequest(AutoInsertRequest.type, param); }; - const disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); + + const disposable = activateAutoInsertion(insertRequestor, languageParticipants, runtime); toDispose.push(disposable); const disposable2 = client.onTelemetry(e => { @@ -193,7 +256,6 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } }); - function updateFormatterRegistration() { const formatEnabled = workspace.getConfiguration().get(SettingIds.formatEnable); if (!formatEnabled && rangeFormatting) { @@ -278,25 +340,12 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } })); - const promptForLinkedEditingKey = 'html.promptForLinkedEditing'; - if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) { - const config = workspace.getConfiguration('editor', { languageId: 'html' }); - if (!config.get('linkedEditing') && !config.get('renameOnType')) { - const activeEditorListener = window.onDidChangeActiveTextEditor(async e => { - if (e && documentSelector.indexOf(e.document.languageId) !== -1) { - context.globalState.update(promptForLinkedEditingKey, false); - activeEditorListener.dispose(); - const configure = localize('configureButton', 'Configure'); - const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); - if (res === configure) { - commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); - } - } - }); - toDispose.push(activeEditorListener); + return { + dispose: async () => { + await client.stop(); + toDispose.forEach(d => d.dispose()); + rangeFormatting?.dispose(); } - } - - return client; + }; } diff --git a/extensions/html-language-features/client/src/languageParticipants.ts b/extensions/html-language-features/client/src/languageParticipants.ts new file mode 100644 index 00000000000..6abe49237c8 --- /dev/null +++ b/extensions/html-language-features/client/src/languageParticipants.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 { DocumentSelector } from 'vscode-languageclient'; +import { Event, EventEmitter, extensions } from 'vscode'; + +/** + * HTML language participant contribution. + */ +interface LanguageParticipantContribution { + /** + * The id of the language which participates with the HTML language server. + */ + languageId: string; + /** + * true if the language activates the auto insertion and false otherwise. + */ + autoInsert?: boolean; +} + +export interface LanguageParticipants { + readonly onDidChange: Event; + readonly documentSelector: DocumentSelector; + hasLanguage(languageId: string): boolean; + useAutoInsert(languageId: string): boolean; + dispose(): void; +} + +export function getLanguageParticipants(): LanguageParticipants { + const onDidChangeEmmiter = new EventEmitter(); + let languages = new Set(); + let autoInsert = new Set(); + + function update() { + const oldLanguages = languages, oldAutoInsert = autoInsert; + + languages = new Set(); + languages.add('html'); + autoInsert = new Set(); + autoInsert.add('html'); + + for (const extension of extensions.allAcrossExtensionHosts) { + const htmlLanguageParticipants = extension.packageJSON?.contributes?.htmlLanguageParticipants as LanguageParticipantContribution[]; + if (Array.isArray(htmlLanguageParticipants)) { + for (const htmlLanguageParticipant of htmlLanguageParticipants) { + const languageId = htmlLanguageParticipant.languageId; + if (typeof languageId === 'string') { + languages.add(languageId); + if (htmlLanguageParticipant.autoInsert !== false) { + autoInsert.add(languageId); + } + } + } + } + } + return !isEqualSet(languages, oldLanguages) || !isEqualSet(oldLanguages, oldAutoInsert); + } + update(); + + const changeListener = extensions.onDidChange(_ => { + if (update()) { + onDidChangeEmmiter.fire(); + } + }); + + return { + onDidChange: onDidChangeEmmiter.event, + get documentSelector() { return Array.from(languages); }, + hasLanguage(languageId: string) { return languages.has(languageId); }, + useAutoInsert(languageId: string) { return autoInsert.has(languageId); }, + dispose: () => changeListener.dispose() + }; +} + +function isEqualSet(s1: Set, s2: Set) { + if (s1.size !== s2.size) { + return false; + } + for (const e of s1) { + if (!s2.has(e)) { + return false; + } + } + return true; +} diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index 11ca6f254f9..c3e8c85cf4f 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -5,15 +5,15 @@ import { getNodeFileFS } from './nodeFs'; import { Disposable, ExtensionContext } from 'vscode'; -import { startClient, LanguageClientConstructor } from '../htmlClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; +import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; import * as fs from 'fs'; import TelemetryReporter from '@vscode/extension-telemetry'; let telemetry: TelemetryReporter | undefined; -let client: BaseLanguageClient | undefined; +let client: AsyncDisposable | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -50,7 +50,7 @@ export async function activate(context: ExtensionContext) { export async function deactivate(): Promise { if (client) { - await client.stop(); + await client.dispose(); client = undefined; } } diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 573b24b4aa6..8f5cef74fd3 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -5,6 +5,7 @@ }, "include": [ "src/**/*", - "../../../src/vscode-dts/vscode.d.ts" + "../../../src/vscode-dts/vscode.d.ts", + "../../../src/vscode-dts/vscode.proposed.extensionsAny.d.ts" ] } diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 16a38088f5b..64e623de887 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -5,7 +5,7 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "0.10.x" }, @@ -14,6 +14,9 @@ "onLanguage:html", "onLanguage:handlebars" ], + "enabledApiProposals": [ + "extensionsAny" + ], "main": "./client/out/node/htmlClientMain", "browser": "./client/dist/browser/htmlClientMain", "capabilities": { @@ -119,12 +122,6 @@ "default": false, "markdownDescription": "%html.format.indentHandlebars.desc%" }, - "html.format.endWithNewline": { - "type": "boolean", - "scope": "resource", - "default": false, - "description": "%html.format.endWithNewline.desc%" - }, "html.format.extraLiners": { "type": [ "string", @@ -263,7 +260,7 @@ "dependencies": { "@vscode/extension-telemetry": "0.5.1", "vscode-languageclient": "^8.0.2-next.4", - "vscode-nls": "^5.0.1", + "vscode-nls": "^5.1.0", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index f5795e33e2e..acb6474d63e 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -10,7 +10,6 @@ "html.format.preserveNewLines.desc": "Controls whether existing line breaks before elements should be preserved. Only works before elements, not inside tags or for text.", "html.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk. Use `null` for unlimited.", "html.format.indentHandlebars.desc": "Format and indent `{{#foo}}` and `{{/foo}}`.", - "html.format.endWithNewline.desc": "End with a newline.", "html.format.extraLiners.desc": "List of tags, comma separated, that should have an extra newline before them. `null` defaults to `\"head, body, /html\"`.", "html.format.wrapAttributes.desc": "Wrap attributes.", "html.format.wrapAttributes.auto": "Wrap attributes only when line length is exceeded.", diff --git a/extensions/html-language-features/schemas/package.schema.json b/extensions/html-language-features/schemas/package.schema.json index a4d8715b918..ef717dbd1d1 100644 --- a/extensions/html-language-features/schemas/package.schema.json +++ b/extensions/html-language-features/schemas/package.schema.json @@ -13,6 +13,23 @@ "type": "string", "description": "Relative path to a HTML custom data file" } + }, + "htmlLanguageParticipants": { + "type": "array", + "description": "A list of languages that participate with the HTML language server.", + "items": { + "type": "object", + "properties": { + "languageId": { + "type": "string", + "description": "The id of the language that participates with HTML language server." + }, + "autoInsert": { + "type": "boolean", + "description": "Whether the language participates with HTML auto insertions. If not specified, defaults to true." + } + } + } } } } diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 322f6b81f85..405e17bbbe2 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,10 +10,10 @@ "main": "./out/node/htmlServerMain", "dependencies": { "vscode-css-languageservice": "^6.0.1", - "vscode-html-languageservice": "^5.0.0", + "vscode-html-languageservice": "^5.0.1", "vscode-languageserver": "^8.0.2-next.4", "vscode-languageserver-textdocument": "^1.0.4", - "vscode-nls": "^5.0.1", + "vscode-nls": "^5.1.0", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 58a3ded2bee..baca7e0369f 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -49,6 +49,9 @@ export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: } else { formatSettings.contentUnformatted = 'script'; } + if (formatParams.insertFinalNewline) { + formatSettings.endWithNewline = true; + } merge(formatParams, formatSettings); return htmlLanguageService.format(document, range, formatSettings); }, diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 10a70dbe99a..0c199571dd7 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -93,13 +93,16 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { }; } +const ignoredErrors = [ + 1108, /* A_return_statement_can_only_be_used_within_a_function_body_1108 */ + 2792, /* Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option */ +]; export function getJavaScriptMode(documentRegions: LanguageModelCache, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode { const jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId)); const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS); const globalSettings: Settings = {}; - return { getId() { return languageId; @@ -110,7 +113,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache d.code !== 1108).map((diag: ts.Diagnostic): Diagnostic => { + return syntaxDiagnostics.concat(semanticDiagnostics).filter(d => !ignoredErrors.includes(d.code)).map((diag: ts.Diagnostic): Diagnostic => { return { range: convertRange(jsDocument, diag), severity: DiagnosticSeverity.Error, diff --git a/extensions/html-language-features/server/src/test/formatting.test.ts b/extensions/html-language-features/server/src/test/formatting.test.ts index 775a7b260fd..ebc319d20b6 100644 --- a/extensions/html-language-features/server/src/test/formatting.test.ts +++ b/extensions/html-language-features/server/src/test/formatting.test.ts @@ -85,16 +85,12 @@ suite('HTML Embedded Formatting', () => { }); test('EndWithNewline', async () => { - const options = { - html: { - format: { - endWithNewline: true - } - } - }; - await assertFormat('

Hello

', '\n\n\n

Hello

\n\n\n\n', options); - await assertFormat('|

Hello

|', '\n

Hello

\n', options); - await assertFormat('', '\n\n\n \n\n\n\n', options); + const options : FormattingOptions = FormattingOptions.create(2, true); + options.insertFinalNewline = true; + + await assertFormat('

Hello

', '\n\n\n

Hello

\n\n\n\n', {}, options); + await assertFormat('|

Hello

|', '\n

Hello

\n', {}, options); + await assertFormat('', '\n\n\n \n\n\n\n', {}, options); }); test('Inside script', async () => { diff --git a/extensions/html-language-features/server/src/utils/documentContext.ts b/extensions/html-language-features/server/src/utils/documentContext.ts index fb1a6622674..88e3f032885 100644 --- a/extensions/html-language-features/server/src/utils/documentContext.ts +++ b/extensions/html-language-features/server/src/utils/documentContext.ts @@ -24,6 +24,10 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp return { resolveReference: (ref: string, base = documentUri) => { + if (ref.match(/^\w[\w\d+.-]*:/)) { + // starts with a schema + return ref; + } if (ref[0] === '/') { // resolve absolute path against the current workspace folder const folderUri = getRootFolder(); if (folderUri) { diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 6f3a8b7b5b1..1c95f5f4056 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -22,10 +22,10 @@ vscode-css-languageservice@^6.0.1: vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-html-languageservice@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.0.tgz#c68613f836d7fcff125183d78e6f1f0ff326fa55" - integrity sha512-KJG13z54aLszskp3ETf8b1EKDypr2Sf5RUsfR6OXmKqEl2ZUfyIxsWz4gbJWjPzoJZx/bGH0ZXVwxJ1rg8OKRQ== +vscode-html-languageservice@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.1.tgz#bdf7847d27a453a9e98ae2836ead7594784c5c1c" + integrity sha512-OYsyn5HGAhxs0OIG+M0jc34WnftLtD67Wg7+TfrYwvf0waOkkr13zUqtdrVm2JPNQ6fJx+qnuM+vTbq7o1dCdQ== dependencies: vscode-languageserver-textdocument "^1.0.4" vscode-languageserver-types "^3.17.1" @@ -72,6 +72,11 @@ vscode-nls@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== + vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index c7c2faeae0f..2d2f101eba6 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -78,10 +78,10 @@ vscode-languageserver-types@3.17.2-next.2: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== -vscode-nls@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 57bbb338210..3beaf419b47 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -10,7 +10,7 @@ "publisher": "vscode", "icon": "icon.png", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.39.0" }, @@ -79,8 +79,8 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.4.10", - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.6.2", + "vscode-nls": "^5.1.0" }, "repository": { "type": "git", diff --git a/extensions/image-preview/yarn.lock b/extensions/image-preview/yarn.lock index 2e316e14a9f..61a6bb946db 100644 --- a/extensions/image-preview/yarn.lock +++ b/extensions/image-preview/yarn.lock @@ -2,12 +2,51 @@ # yarn lockfile v1 -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" + +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/ini/package.json b/extensions/ini/package.json index 324e4c87f6e..aa98ee3a353 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -37,7 +37,8 @@ ".editorconfig" ], "filenames": [ - "gitconfig" + "gitconfig", + ".env" ], "filenamePatterns": [ "**/.config/git/config", diff --git a/extensions/ipynb/.gitignore b/extensions/ipynb/.gitignore index 170d890802b..8c0ca40ca20 100644 --- a/extensions/ipynb/.gitignore +++ b/extensions/ipynb/.gitignore @@ -2,3 +2,4 @@ out dist node_modules *.vsix +notebook-out diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index 08245c01482..69a1b29e0ce 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -1,9 +1,10 @@ .vscode/** src/** +notebook-src/** out/** tsconfig.json extension.webpack.config.js extension-browser.webpack.config.js yarn.lock .gitignore - +esbuild.js diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js new file mode 100644 index 00000000000..4128889bf76 --- /dev/null +++ b/extensions/ipynb/esbuild.js @@ -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. + *--------------------------------------------------------------------------------------------*/ +//@ts-check + +const path = require('path'); +const fse = require('fs-extra'); +const esbuild = require('esbuild'); + +const args = process.argv.slice(2); + +const isWatch = args.indexOf('--watch') >= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const srcDir = path.join(__dirname, 'notebook-src'); +const outDir = path.join(outputRoot, 'notebook-out'); + +async function build() { + await esbuild.build({ + entryPoints: [ + path.join(srcDir, 'cellAttachmentRenderer.ts'), + ], + bundle: true, + minify: false, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + }); +} + + +build().catch(() => process.exit(1)); + +if (isWatch) { + const watcher = require('@parcel/watcher'); + watcher.subscribe(srcDir, () => { + return build(); + }); +} diff --git a/extensions/ipynb/notebook-src/cellAttachmentRenderer.ts b/extensions/ipynb/notebook-src/cellAttachmentRenderer.ts new file mode 100644 index 00000000000..722f6182708 --- /dev/null +++ b/extensions/ipynb/notebook-src/cellAttachmentRenderer.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import type * as MarkdownIt from 'markdown-it'; +import type * as MarkdownItToken from 'markdown-it/lib/token'; +import type { RendererContext } from 'vscode-notebook-renderer'; + +interface MarkdownItRenderer { + extendMarkdownIt(fn: (md: MarkdownIt) => void): void; +} + +export async function activate(ctx: RendererContext) { + const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as MarkdownItRenderer | any; + if (!markdownItRenderer) { + throw new Error(`Could not load 'vscode.markdown-it-renderer'`); + } + + markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => { + const original = md.renderer.rules.image; + md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { + const token = tokens[idx]; + const src = token.attrGet('src'); + const attachments: Record> = env.outputItem.metadata.custom?.attachments; + if (attachments && src) { + const imageAttachment = attachments[src.replace('attachment:', '')]; + if (imageAttachment) { + // objEntries will always be length 1, with objEntries[0] holding [0]=mime,[1]=b64 + // if length = 0, something is wrong with the attachment, mime/b64 weren't copied over + const objEntries = Object.entries(imageAttachment); + if (objEntries.length) { + const [attachmentKey, attachmentVal] = objEntries[0]; + const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal; + token.attrSet('src', b64Markdown); + } + } + } + + if (original) { + return original(tokens, idx, options, env, self); + } else { + return self.renderToken(tokens, idx, options); + } + }; + }); +} diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 1e251a5a19b..ef01fa6baa6 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -9,7 +9,8 @@ "vscode": "^1.57.0" }, "enabledApiProposals": [ - "notebookWorkspaceEdit" + "notebookWorkspaceEdit", + "documentPaste" ], "activationEvents": [ "*" @@ -27,6 +28,21 @@ } }, "contributes": { + "configuration":[ + { + "properties": { + "ipynb.experimental.pasteImages.enabled":{ + "type": "boolean", + "scope": "resource", + "markdownDescription": "%ipynb.experimental.pasteImages.enabled%", + "default": false, + "tags": [ + "experimental" + ] + } + } + } + ], "commands": [ { "command": "ipynb.newUntitledIpynb", @@ -51,6 +67,16 @@ "priority": "default" } ], + "notebookRenderer": [ + { + "id": "vscode.markdown-it-cell-attachment-renderer", + "displayName": "Markdown it ipynb Cell Attachment renderer", + "entrypoint": { + "extends": "vscode.markdown-it-renderer", + "path": "./notebook-out/cellAttachmentRenderer.js" + } + } + ], "menus": { "file/newFile": [ { @@ -70,8 +96,9 @@ } }, "scripts": { - "compile": "npx gulp compile-extension:ipynb", - "watch": "npx gulp watch-extension:ipynb" + "compile": "npx gulp compile-extension:ipynb && npm run build-notebook", + "watch": "npx gulp watch-extension:ipynb", + "build-notebook": "node ./esbuild" }, "dependencies": { "@enonic/fnv-plus": "^1.3.0", @@ -80,6 +107,7 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", + "@types/markdown-it": "12.2.3", "@types/uuid": "^8.3.1" }, "repository": { diff --git a/extensions/ipynb/package.nls.json b/extensions/ipynb/package.nls.json index 6f2f7e47c0c..fc7592584de 100644 --- a/extensions/ipynb/package.nls.json +++ b/extensions/ipynb/package.nls.json @@ -1,4 +1,5 @@ { "displayName": ".ipynb support", - "description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files" + "description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files", + "ipynb.experimental.pasteImages.enabled":"Enable/Disable pasting images into markdown cells within ipynb files. Requires enabling `#ipynb.experimental.pasteImages.enabled#`." } diff --git a/extensions/ipynb/src/cellIdService.ts b/extensions/ipynb/src/cellIdService.ts index ddda0a9fd5f..2eccb586ef4 100644 --- a/extensions/ipynb/src/cellIdService.ts +++ b/extensions/ipynb/src/cellIdService.ts @@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid'; import { getCellMetadata } from './serializers'; import { CellMetadata } from './common'; import { getNotebookMetadata } from './notebookSerializer'; -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; /** * Ensure all new cells in notebooks with nbformat >= 4.5 have an id. diff --git a/extensions/ipynb/src/common.ts b/extensions/ipynb/src/common.ts index de72fbbbfc3..d5ff5f86069 100644 --- a/extensions/ipynb/src/common.ts +++ b/extensions/ipynb/src/common.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; /** * Metadata we store in VS Code cell output items. @@ -44,7 +44,7 @@ export interface CellOutputMetadata { /** * Metadata we store in VS Code cells. - * This contains the original metadata from the Jupyuter cells. + * This contains the original metadata from the Jupyter cells. */ export interface CellMetadata { /** diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index 50a3a1271b0..92cd20bb33a 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import { extensions, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode'; import { CellMetadata, CellOutputMetadata } from './common'; diff --git a/extensions/ipynb/src/ipynbMain.ts b/extensions/ipynb/src/ipynbMain.ts index 33bb1456af8..003c4c8f266 100644 --- a/extensions/ipynb/src/ipynbMain.ts +++ b/extensions/ipynb/src/ipynbMain.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { ensureAllNewCellsHaveCellIds } from './cellIdService'; import { NotebookSerializer } from './notebookSerializer'; +import * as NotebookImagePaste from './notebookImagePaste'; // From {nbformat.INotebookMetadata} in @jupyterlab/coreutils type NotebookMetadata = { @@ -77,12 +78,15 @@ export function activate(context: vscode.ExtensionContext) { await vscode.window.showNotebookDocument(document); })); + context.subscriptions.push(NotebookImagePaste.imagePasteSetup()); + // Update new file contribution vscode.extensions.onDidChange(() => { vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); }); vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); + return { exportNotebook: (notebook: vscode.NotebookData): string => { return exportNotebook(notebook, serializer); diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts new file mode 100644 index 00000000000..64137f82300 --- /dev/null +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +class CopyPasteEditProvider implements vscode.DocumentPasteEditProvider { + + async provideDocumentPasteEdits( + _document: vscode.TextDocument, + _ranges: readonly vscode.Range[], + dataTransfer: vscode.DataTransfer, + _token: vscode.CancellationToken + ): Promise { + + const enabled = vscode.workspace.getConfiguration('ipynb', _document).get('experimental.pasteImages.enabled', false); + if (!enabled) { + return undefined; + } + + // get b64 data from paste + // TODO: dataTransfer.get() limits to one image pasted + const dataItem = dataTransfer.get('image/png'); + if (!dataItem) { + return undefined; + } + const fileDataAsUint8 = await dataItem.asFile()?.data(); + if (!fileDataAsUint8) { + return undefined; + } + + // get filename data from paste + let pasteFilename = dataItem.asFile()?.name; + if (!pasteFilename) { + return undefined; + } + const separatorIndex = pasteFilename?.lastIndexOf('.'); + const filename = pasteFilename?.slice(0, separatorIndex); + const filetype = pasteFilename?.slice(separatorIndex); + if (!filename || !filetype) { + return undefined; + } + + // get notebook cell data + let notebookUri; + let currentCell; + for (const notebook of vscode.workspace.notebookDocuments) { + if (notebook.uri.path === _document.uri.path) { + for (const cell of notebook.getCells()) { + if (cell.document === _document) { + currentCell = cell; + notebookUri = notebook.uri; + break; + } + } + } + } + if (!currentCell || !notebookUri) { + return undefined; + } + + // create updated metadata for cell (prep for WorkspaceEdit) + const b64string = encodeBase64(fileDataAsUint8); + const startingAttachments = currentCell.metadata?.custom?.attachments; + if (!startingAttachments) { + currentCell.metadata.custom['attachments'] = { [pasteFilename]: { 'image/png': b64string } }; + } else { + for (let appendValue = 2; pasteFilename in startingAttachments; appendValue++) { + const objEntries = Object.entries(startingAttachments[pasteFilename]); + if (objEntries.length) { // check that mime:b64 are present + const [, attachmentb64] = objEntries[0]; + if (attachmentb64 !== b64string) { // append a "-#" here. same name, diff data. this matches jupyter behavior + pasteFilename = filename.concat(`-${appendValue}`) + filetype; + } + } + } + currentCell.metadata.custom.attachments[pasteFilename] = { 'image/png': b64string }; + } + + const metadataNotebookEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, currentCell.metadata); + const workspaceEdit = new vscode.WorkspaceEdit(); + if (metadataNotebookEdit) { + workspaceEdit.set(notebookUri, [metadataNotebookEdit]); + } + + // create a snippet for paste + const pasteSnippet = new vscode.SnippetString(); + pasteSnippet.appendText('!['); + pasteSnippet.appendPlaceholder(`${pasteFilename}`); + pasteSnippet.appendText(`](attachment:${pasteFilename})`); + + return { insertText: pasteSnippet, additionalEdit: workspaceEdit }; + } +} + +export function imagePasteSetup() { + const selector: vscode.DocumentSelector = { notebookType: 'jupyter-notebook', language: 'markdown' }; // this is correct provider + return vscode.languages.registerDocumentPasteEditProvider(selector, new CopyPasteEditProvider(), { + pasteMimeTypes: ['image/png'], + }); +} + +/** + * Taken from https://github.com/microsoft/vscode/blob/743b016722db90df977feecde0a4b3b4f58c2a4c/src/vs/base/common/buffer.ts#L350-L387 + */ +function encodeBase64(buffer: Uint8Array, padded = true, urlSafe = false) { + const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const base64UrlSafeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + + const dictionary = urlSafe ? base64UrlSafeAlphabet : base64Alphabet; + let output = ''; + + const remainder = buffer.byteLength % 3; + + let i = 0; + for (; i < buffer.byteLength - remainder; i += 3) { + const a = buffer[i + 0]; + const b = buffer[i + 1]; + const c = buffer[i + 2]; + + output += dictionary[a >>> 2]; + output += dictionary[(a << 4 | b >>> 4) & 0b111111]; + output += dictionary[(b << 2 | c >>> 6) & 0b111111]; + output += dictionary[c & 0b111111]; + } + + if (remainder === 1) { + const a = buffer[i + 0]; + output += dictionary[a >>> 2]; + output += dictionary[(a << 4) & 0b111111]; + if (padded) { output += '=='; } + } else if (remainder === 2) { + const a = buffer[i + 0]; + const b = buffer[i + 1]; + output += dictionary[a >>> 2]; + output += dictionary[(a << 4 | b >>> 4) & 0b111111]; + output += dictionary[(b << 2) & 0b111111]; + if (padded) { output += '='; } + } + + return output; +} diff --git a/extensions/ipynb/src/notebookSerializer.ts b/extensions/ipynb/src/notebookSerializer.ts index 37eee49b1be..0c3ccd09723 100644 --- a/extensions/ipynb/src/notebookSerializer.ts +++ b/extensions/ipynb/src/notebookSerializer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import * as detectIndent from 'detect-indent'; import * as vscode from 'vscode'; import { defaultNotebookFormat } from './constants'; diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts index 078bb408cc5..455fb0d2745 100644 --- a/extensions/ipynb/src/serializers.ts +++ b/extensions/ipynb/src/serializers.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import { NotebookCell, NotebookCellData, NotebookCellKind, NotebookCellOutput } from 'vscode'; import { CellMetadata, CellOutputMetadata } from './common'; import { textMimeTypes } from './deserializers'; diff --git a/extensions/ipynb/src/test/serializers.test.ts b/extensions/ipynb/src/test/serializers.test.ts index 15ef1185f94..2a59c73532e 100644 --- a/extensions/ipynb/src/test/serializers.test.ts +++ b/extensions/ipynb/src/test/serializers.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import * as assert from 'assert'; import * as vscode from 'vscode'; import { jupyterCellOutputToCellOutput, jupyterNotebookModelToNotebookData } from '../deserializers'; diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index a8006017458..50d718f424e 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -9,6 +9,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts" + "../../src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts", + "../../src/vscode-dts/vscode.proposed.documentPaste.d.ts" ] } diff --git a/extensions/ipynb/yarn.lock b/extensions/ipynb/yarn.lock index c932570ecc0..7b5488e7109 100644 --- a/extensions/ipynb/yarn.lock +++ b/extensions/ipynb/yarn.lock @@ -8,21 +8,39 @@ integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw== "@jupyterlab/nbformat@^3.2.9": - version "3.2.9" - resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz#e7d854719612133498af4280d9a8caa0873205b0" - integrity sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w== + version "3.4.3" + resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.4.3.tgz#cbab1bf507677b7f0f309d8353fc83fe5a973c82" + integrity sha512-i/yADrwhhAJJCUOTa+fEBMyJO7fvX9Y73I0B7V6dQhGcrmrEKLC3wk4yOo63+jRntd5+dupbiOtz3w1ncIXwIA== dependencies: - "@lumino/coreutils" "^1.5.3" + "@lumino/coreutils" "^1.11.0" -"@lumino/coreutils@^1.5.3": +"@lumino/coreutils@^1.11.0": version "1.12.0" resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15" integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ== +"@types/linkify-it@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + +"@types/markdown-it@12.2.3": + version "12.2.3" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" + integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + +"@types/mdurl@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + "@types/uuid@^8.3.1": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== detect-indent@^6.0.0: version "6.1.0" diff --git a/extensions/jake/package.json b/extensions/jake/package.json index 0e5ebc4ea8b..5ffa78e3981 100644 --- a/extensions/jake/package.json +++ b/extensions/jake/package.json @@ -17,7 +17,7 @@ "watch": "gulp watch-extension:jake" }, "dependencies": { - "vscode-nls": "^4.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/jake/yarn.lock b/extensions/jake/yarn.lock index 22c406bc73f..90475ddc7e0 100644 --- a/extensions/jake/yarn.lock +++ b/extensions/jake/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 60225af8cef..87131049763 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -9,19 +9,19 @@ const localize = nls.loadMessageBundle(); export type JSONLanguageStatus = { schemas: string[] }; import { - workspace, window, languages, commands, ExtensionContext, extensions, Uri, - Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, - ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, + workspace, window, languages, commands, ExtensionContext, extensions, Uri, ColorInformation, + Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange, + ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation } from 'vscode'; import { LanguageClientOptions, RequestType, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient, ProvideFoldingRangeSignature, ProvideDocumentSymbolsSignature, ProvideDocumentColorsSignature } from 'vscode-languageclient'; import { hash } from './utils/hash'; -import { createLanguageStatusItem } from './languageStatus'; +import { createDocumentColorsLimitItem, createDocumentSymbolsLimitItem, createFoldingRangeLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; namespace VSCodeContentRequest { export const type: RequestType = new RequestType('vscode/content'); @@ -53,14 +53,11 @@ namespace SchemaAssociationNotification { export const type: NotificationType = new NotificationType('json/schemaAssociations'); } -namespace ResultLimitReachedNotification { - export const type: NotificationType = new NotificationType('json/resultLimitReached'); -} - type Settings = { json?: { schemas?: JSONSchemaSettings[]; format?: { enable?: boolean }; + keepLines?: { enable?: boolean }; validate?: { enable?: boolean }; resultLimit?: number; }; @@ -76,17 +73,14 @@ export type JSONSchemaSettings = { schema?: any; }; -namespace SettingIds { +export namespace SettingIds { export const enableFormatter = 'json.format.enable'; + export const enableKeepLines = 'json.format.keepLines'; export const enableValidation = 'json.validate.enable'; export const enableSchemaDownload = 'json.schemaDownload.enable'; export const maxItemsComputed = 'json.maxItemsComputed'; } -namespace StorageIds { - export const maxItemsExceededInformation = 'json.maxItemsExceededInformation'; -} - export interface TelemetryReporter { sendTelemetryEvent(eventName: string, properties?: { [key: string]: string; @@ -109,6 +103,8 @@ export interface SchemaRequestService { export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); +let resultLimit = 5000; + export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const toDispose = context.subscriptions; @@ -127,6 +123,11 @@ export async function startClient(context: ExtensionContext, newLanguageClient: let isClientReady = false; + const foldingRangeLimitStatusBarItem = createLimitStatusItem((limit: number) => createFoldingRangeLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); + const documentSymbolsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentSymbolsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); + const documentColorsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentColorsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); + toDispose.push(foldingRangeLimitStatusBarItem, documentSymbolsLimitStatusbarItem, documentColorsLimitStatusbarItem); + toDispose.push(commands.registerCommand('json.clearCache', async () => { if (isClientReady && runtime.schemaRequests.clearCache) { const cachedSchemas = await runtime.schemaRequests.clearCache(); @@ -211,6 +212,60 @@ export async function startClient(context: ExtensionContext, newLanguageClient: return r.then(updateHover); } return updateHover(r); + }, + provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken, next: ProvideFoldingRangeSignature) { + function checkLimit(r: FoldingRange[] | null | undefined): FoldingRange[] | null | undefined { + if (Array.isArray(r) && r.length > resultLimit) { + r.length = resultLimit; // truncate + foldingRangeLimitStatusBarItem.update(document, resultLimit); + } else { + foldingRangeLimitStatusBarItem.update(document, false); + } + return r; + } + const r = next(document, context, token); + if (isThenable(r)) { + return r.then(checkLimit); + } + return checkLimit(r); + }, + provideDocumentColors(document: TextDocument, token: CancellationToken, next: ProvideDocumentColorsSignature) { + function checkLimit(r: ColorInformation[] | null | undefined): ColorInformation[] | null | undefined { + if (Array.isArray(r) && r.length > resultLimit) { + r.length = resultLimit; // truncate + documentColorsLimitStatusbarItem.update(document, resultLimit); + } else { + documentColorsLimitStatusbarItem.update(document, false); + } + return r; + } + const r = next(document, token); + if (isThenable(r)) { + return r.then(checkLimit); + } + return checkLimit(r); + }, + provideDocumentSymbols(document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) { + type T = SymbolInformation[] | DocumentSymbol[]; + function countDocumentSymbols(symbols: DocumentSymbol[]): number { + return symbols.reduce((previousValue, s) => previousValue + 1 + countDocumentSymbols(s.children), 0); + } + function isDocumentSymbol(r: T): r is DocumentSymbol[] { + return r[0] instanceof DocumentSymbol; + } + function checkLimit(r: T | null | undefined): T | null | undefined { + if (Array.isArray(r) && (isDocumentSymbol(r) ? countDocumentSymbols(r) : r.length) > resultLimit) { + documentSymbolsLimitStatusbarItem.update(document, resultLimit); + } else { + documentSymbolsLimitStatusbarItem.update(document, false); + } + return r; + } + const r = next(document, token); + if (isThenable(r)) { + return r.then(checkLimit); + } + return checkLimit(r); } } }; @@ -328,21 +383,6 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } })); - client.onNotification(ResultLimitReachedNotification.type, async message => { - const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; - if (shouldPrompt) { - const ok = localize('ok', "OK"); - const openSettings = localize('goToSetting', 'Open Settings'); - const neverAgain = localize('yes never again', "Don't Show Again"); - const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); - if (pick === neverAgain) { - await context.globalState.update(StorageIds.maxItemsExceededInformation, false); - } else if (pick === openSettings) { - await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); - } - } - }); - toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); function updateFormatterRegistration() { @@ -432,7 +472,7 @@ function getSettings(): Settings { const configuration = workspace.getConfiguration(); const httpSettings = workspace.getConfiguration('http'); - const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000; + resultLimit = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000; const settings: Settings = { http: { @@ -442,8 +482,9 @@ function getSettings(): Settings { json: { validate: { enable: configuration.get(SettingIds.enableValidation) }, format: { enable: configuration.get(SettingIds.enableFormatter) }, + keepLines: { enable: configuration.get(SettingIds.enableKeepLines) }, schemas: [], - resultLimit + resultLimit: resultLimit + 1 // ask for one more so we can detect if the limit has been exceeded } }; const schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts index 152759c39a1..c9e45894af2 100644 --- a/extensions/json-language-features/client/src/languageStatus.ts +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -3,7 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, ThemeIcon } from 'vscode'; +import { + window, languages, Uri, Disposable, commands, QuickPickItem, + extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, + ThemeIcon, TextDocument, LanguageStatusSeverity +} from 'vscode'; import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient'; import * as nls from 'vscode-nls'; @@ -187,13 +191,13 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque statusItem.detail = undefined; if (schemas.length === 0) { statusItem.text = localize('status.noSchema.short', "No Schema Validation"); - statusItem.detail = localize('status.noSchema', 'No JSON schema configured.'); + statusItem.detail = localize('status.noSchema', 'no JSON schema configured'); } else if (schemas.length === 1) { statusItem.text = localize('status.withSchema.short', "Schema Validated"); - statusItem.detail = localize('status.singleSchema', 'JSON schema configured.'); + statusItem.detail = localize('status.singleSchema', 'JSON schema configured'); } else { statusItem.text = localize('status.withSchemas.short', "Schema Validated"); - statusItem.detail = localize('status.multipleSchema', 'Multiple JSON schemas configured.'); + statusItem.detail = localize('status.multipleSchema', 'multiple JSON schemas configured'); } statusItem.command = { command: '_json.showAssociatedSchemaList', @@ -217,3 +221,85 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque return Disposable.from(statusItem, activeEditorListener, showSchemasCommand); } +export function createLimitStatusItem(newItem: (limit: number) => Disposable) { + let statusItem: Disposable | undefined; + const activeLimits: Map = new Map(); + + const toDispose: Disposable[] = []; + toDispose.push(window.onDidChangeActiveTextEditor(textEditor => { + statusItem?.dispose(); + statusItem = undefined; + const doc = textEditor?.document; + if (doc) { + const limit = activeLimits.get(doc); + if (limit !== undefined) { + statusItem = newItem(limit); + } + } + })); + toDispose.push(workspace.onDidCloseTextDocument(document => { + activeLimits.delete(document); + })); + + function update(document: TextDocument, limitApplied: number | false) { + if (limitApplied === false) { + activeLimits.delete(document); + if (statusItem && document === window.activeTextEditor?.document) { + statusItem.dispose(); + statusItem = undefined; + } + } else { + activeLimits.set(document, limitApplied); + if (document === window.activeTextEditor?.document) { + if (!statusItem || limitApplied !== activeLimits.get(document)) { + statusItem?.dispose(); + statusItem = newItem(limitApplied); + } + } + } + } + return { + update, + dispose() { + statusItem?.dispose(); + toDispose.forEach(d => d.dispose()); + toDispose.length = 0; + statusItem = undefined; + activeLimits.clear(); + } + }; +} + +const openSettingsCommand = 'workbench.action.openSettings'; +const configureSettingsLabel = localize('status.button.configure', "Configure"); + +export function createFoldingRangeLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { + const statusItem = languages.createLanguageStatusItem('json.foldingRangesStatus', documentSelector); + statusItem.name = localize('foldingRangesStatusItem.name', "JSON Folding Status"); + statusItem.severity = LanguageStatusSeverity.Warning; + statusItem.text = localize('status.limitedFoldingRanges.short', "Folding Ranges Limited"); + statusItem.detail = localize('status.limitedFoldingRanges.details', 'only {0} folding ranges shown', limit); + statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; + return Disposable.from(statusItem); +} + +export function createDocumentSymbolsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { + const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector); + statusItem.name = localize('documentSymbolsStatusItem.name', "JSON Outline Status"); + statusItem.severity = LanguageStatusSeverity.Warning; + statusItem.text = localize('status.limitedDocumentSymbols.short', "Outline Limited"); + statusItem.detail = localize('status.limitedDocumentSymbols.details', 'only {0} document symbols shown', limit); + statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; + return Disposable.from(statusItem); +} + +export function createDocumentColorsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { + const statusItem = languages.createLanguageStatusItem('json.documentColorsStatus', documentSelector); + statusItem.name = localize('documentColorsStatusItem.name', "JSON Color Symbol Status"); + statusItem.severity = LanguageStatusSeverity.Warning; + statusItem.text = localize('status.limitedDocumentColors.short', "Color Symbols Limited"); + statusItem.detail = localize('status.limitedDocumentColors.details', 'only {0} color decorators shown', limit); + statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; + return Disposable.from(statusItem); +} + diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 1a5d679e950..81a51598b1f 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -5,7 +5,7 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "0.10.x" }, @@ -85,6 +85,12 @@ "default": true, "description": "%json.format.enable.desc%" }, + "json.format.keepLines": { + "type": "boolean", + "scope": "window", + "default": false, + "description": "%json.format.keepLines.desc%" + }, "json.trace.server": { "type": "string", "scope": "window", @@ -147,10 +153,10 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "0.5.1", + "@vscode/extension-telemetry": "0.6.2", "request-light": "^0.5.8", - "vscode-languageclient": "^8.0.2-next.4", - "vscode-nls": "^5.0.1" + "vscode-languageclient": "^8.0.2-next.5", + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 8afec56a90f..a0d0a84b32c 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -7,6 +7,7 @@ "json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.", "json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.", "json.format.enable.desc": "Enable/disable default JSON formatter", + "json.format.keepLines.desc" : "Keep all existing new lines when formatting.", "json.validate.enable.desc": "Enable/disable JSON validation.", "json.tracing.desc": "Traces the communication between VS Code and the JSON language server.", "json.colorDecorators.enable.desc": "Enables or disables color decorators", diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index e82ae06d776..bc9b22e4ffe 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -188,12 +188,6 @@ Notification: ### Item Limit If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed. -When the limit is reached, a notification `json/resultLimitReached` is sent that can be shown that can be shown to the user. - -Notification: -- method: 'json/resultLimitReached' -- params: a human readable string to show to the user. - ## Try diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index ae471f515cd..abae73c4080 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,10 +12,10 @@ }, "main": "./out/node/jsonServerMain", "dependencies": { - "jsonc-parser": "^3.0.0", + "jsonc-parser": "^3.1.0", "request-light": "^0.5.8", - "vscode-json-languageservice": "^5.0.0", - "vscode-languageserver": "^8.0.2-next.4", + "vscode-json-languageservice": "^5.1.0", + "vscode-languageserver": "^8.0.2-next.5", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 2da36da1054..05910009b3b 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -31,10 +31,6 @@ namespace SchemaContentChangeNotification { export const type: NotificationType = new NotificationType('json/schemaContent'); } -namespace ResultLimitReachedNotification { - export const type: NotificationType = new NotificationType('json/resultLimitReached'); -} - namespace ForceValidateRequest { export const type: RequestType = new RequestType('json/validate'); } @@ -188,6 +184,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) json?: { schemas?: JSONSchemaSettings[]; format?: { enable?: boolean }; + keepLines?: { enable?: boolean }; validate?: { enable?: boolean }; resultLimit?: number; }; @@ -204,56 +201,20 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } - const limitExceededWarnings = function () { - const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: Disposable } } = {}; - - const showLimitedNotification = (uri: string, resultLimit: number) => { - const warning = pendingWarnings[uri]; - connection.sendNotification(ResultLimitReachedNotification.type, `${Utils.basename(URI.parse(uri))}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); - warning.timeout = undefined; - }; - - return { - cancel(uri: string) { - const warning = pendingWarnings[uri]; - if (warning && warning.timeout) { - warning.timeout.dispose(); - delete pendingWarnings[uri]; - } - }, - - onResultLimitExceeded(uri: string, resultLimit: number, name: string) { - return () => { - let warning = pendingWarnings[uri]; - if (warning) { - if (!warning.timeout) { - // already shown - return; - } - warning.features[name] = name; - warning.timeout.dispose(); - warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000); - } else { - warning = { features: { [name]: name } }; - warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000); - pendingWarnings[uri] = warning; - } - }; - } - }; - }(); let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; let schemaAssociations: ISchemaAssociations | SchemaConfiguration[] | undefined = undefined; let formatterRegistrations: Thenable[] | null = null; let validateEnabled = true; + let keepLinesEnabled = false; - // The settings have changed. Is send on server activation as well. + // The settings have changed. Is sent on server activation as well. connection.onDidChangeConfiguration((change) => { const settings = change.settings; runtime.configureHttpRequests?.(settings?.http?.proxy, !!settings.http?.proxyStrictSSL); jsonConfigurationSettings = settings.json?.schemas; validateEnabled = !!settings.json?.validate?.enable; + keepLinesEnabled = settings.json?.keepLines?.enable || false; updateConfiguration(); foldingRangeLimit = Math.trunc(Math.max(settings.json?.resultLimit || foldingRangeLimitDefault, 0)); @@ -417,11 +378,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const document = documents.get(documentSymbolParams.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); - const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document symbols'); if (hierarchicalDocumentSymbolSupport) { - return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit, onResultLimitExceeded }); + return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit }); } else { - return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit, onResultLimitExceeded }); + return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit }); } } return []; @@ -429,6 +389,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): TextEdit[] { + options.keepLines = keepLinesEnabled; const document = documents.get(textDocument.uri); if (document) { const edits = languageService.format(document, range ?? getFullRange(document), options); @@ -453,9 +414,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return runSafeAsync(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { - const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors'); + const jsonDocument = getJSONDocument(document); - return languageService.findDocumentColors(document, jsonDocument, { resultLimit, onResultLimitExceeded }); + return languageService.findDocumentColors(document, jsonDocument, { resultLimit }); } return []; }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); @@ -476,8 +437,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return runSafe(runtime, () => { const document = documents.get(params.textDocument.uri); if (document) { - const onRangeLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges'); - return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit, onRangeLimitExceeded }); + return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); } return null; }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 83a722965dd..e0b300c908f 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -12,22 +12,22 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -jsonc-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" - integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== +jsonc-parser@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.1.0.tgz#73b8f0e5c940b83d03476bc2e51a20ef0932615d" + integrity sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg== request-light@^0.5.8: version "0.5.8" resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== -vscode-json-languageservice@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.0.0.tgz#465d76cfe5dfeed4c3d5a2123b50e3f115bb7f78" - integrity sha512-1/+1TJBRFrfCNizmrW0fbIvguKzzO+4ehlqWCCnF7ioSACUGHrYop4ANb+eRnFaCP6fi3+i+llJC5Y5yAvmL6w== +vscode-json-languageservice@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.1.0.tgz#b1f197a60338cb378189fcb41489a84846724dd9" + integrity sha512-D5612D7h/Gh4A0JmdttPveWzT9dur21WXvBHWKPdOt0sLO6ILz8vN6+IzWnvwDOVAEFTpzIAMVMZwbKZkwGGiA== dependencies: - jsonc-parser "^3.0.0" + jsonc-parser "^3.1.0" vscode-languageserver-textdocument "^1.0.4" vscode-languageserver-types "^3.17.1" vscode-nls "^5.0.1" @@ -38,10 +38,10 @@ vscode-jsonrpc@8.0.2-next.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== -vscode-languageserver-protocol@3.17.2-next.5: - version "3.17.2-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.5.tgz#9bc747411c3ce9e1d73c2714bf6555e0199eec26" - integrity sha512-UlH+QL4Q4lX94of/UPDDwwWIkd8w7dtMW4khzvEDUoykiG9tba0iG6V0bAiv8XVpnBIUYjL2FNFiL3zl+TY1Sw== +vscode-languageserver-protocol@3.17.2-next.6: + version "3.17.2-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" + integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== dependencies: vscode-jsonrpc "8.0.2-next.1" vscode-languageserver-types "3.17.2-next.2" @@ -61,12 +61,12 @@ vscode-languageserver-types@^3.17.1: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^8.0.2-next.4: - version "8.0.2-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.4.tgz#c10cc95be06325b56b7ec1d10271c9e4adf3ef07" - integrity sha512-B3roWH4TmJiB6Zh5+r7zu0QdlLqJsPdGo0LeEi6OiLfrHYCDlcI7DNcQ7F17vWmxC3C82SrxMt/EuLBMpKQM0A== +vscode-languageserver@^8.0.2-next.5: + version "8.0.2-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5" + integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A== dependencies: - vscode-languageserver-protocol "3.17.2-next.5" + vscode-languageserver-protocol "3.17.2-next.6" vscode-nls@^5.0.1: version "5.0.1" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 983804a240d..e7574fd2c50 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,15 +2,54 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.1.tgz#20150976629663b3d33799a4ad25944a1535f7db" - integrity sha512-cvFq8drxdLRF8KN72WcV4lTEa9GqDiRwy9EbnYuoSCD9Jdk8zHFF49MmACC1qs4R9Ko/C1uMOmeLJmVi8EA0rQ== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" balanced-match@^1.0.0: version "1.0.0" @@ -61,19 +100,19 @@ vscode-jsonrpc@8.0.2-next.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== -vscode-languageclient@^8.0.2-next.4: - version "8.0.2-next.4" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2-next.4.tgz#87dd364ffbd4356aff3af14e7b557d9fe34d2b67" - integrity sha512-j9BEiCYMN9IoKwYdk9iickV6WNPVGPoVO11SMdoxFnWPIT3y5UAe3qf/WsfA9OdklAIaxxYasfgyKCpBjSPNuw== +vscode-languageclient@^8.0.2-next.5: + version "8.0.2-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2-next.5.tgz#3238a388585c3119e247f761b4355273cc2fd909" + integrity sha512-g87RJLHz0XlRyk6DOTbAk4JHcj8CKggXy4JiFL7OlhETkcYzTOR8d+Qdb4GqZr37PDs1Cl21omtTNK5LyR/RQg== dependencies: minimatch "^3.0.4" semver "^7.3.5" - vscode-languageserver-protocol "3.17.2-next.5" + vscode-languageserver-protocol "3.17.2-next.6" -vscode-languageserver-protocol@3.17.2-next.5: - version "3.17.2-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.5.tgz#9bc747411c3ce9e1d73c2714bf6555e0199eec26" - integrity sha512-UlH+QL4Q4lX94of/UPDDwwWIkd8w7dtMW4khzvEDUoykiG9tba0iG6V0bAiv8XVpnBIUYjL2FNFiL3zl+TY1Sw== +vscode-languageserver-protocol@3.17.2-next.6: + version "3.17.2-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" + integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== dependencies: vscode-jsonrpc "8.0.2-next.1" vscode-languageserver-types "3.17.2-next.2" @@ -83,10 +122,10 @@ vscode-languageserver-types@3.17.2-next.2: resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== -vscode-nls@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== yallist@^4.0.0: version "4.0.0" diff --git a/extensions/json/language-configuration.json b/extensions/json/language-configuration.json index 2dc97d50dbb..f9ec3fec781 100644 --- a/extensions/json/language-configuration.json +++ b/extensions/json/language-configuration.json @@ -16,7 +16,7 @@ { "open": "`", "close": "`", "notIn": ["string", "comment"] } ], "indentationRules": { - "increaseIndentPattern": "({+(?=([^\"]*\"[^\"]*\")*[^\"}]*$))|(\\[+(?=([^\"]*\"[^\"]*\")*[^\"\\]]*$))", + "increaseIndentPattern": "({+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"}]*)$)|(\\[+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"\\]]*)$)", "decreaseIndentPattern": "^\\s*[}\\]],?\\s*$" } } diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index cb6266c338a..766da20f828 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "8f2c528619535333a2ff12e321fb6f6efcba38e4" + "commitHash": "ffe263692d6096b24e4897650e933c587fa9c55f" } }, "license": "MIT", diff --git a/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/extensions/latex/syntaxes/LaTeX.tmLanguage.json index e2061e78fa2..7cf93fdbe6a 100644 --- a/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/8f2c528619535333a2ff12e321fb6f6efcba38e4", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/6fc051150e918f8e5df7b102977d8d72eedf66f6", "name": "LaTeX", "scopeName": "text.tex.latex", "patterns": [ @@ -119,7 +119,7 @@ "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -160,12 +160,12 @@ }, "patterns": [ { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:cpp|c)\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:c|cpp)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -179,12 +179,31 @@ "end": "(\\\\end\\{minted\\})" }, { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{css\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:asy|asymptote)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.asy", + "patterns": [ + { + "include": "source.asy" + } + ], + "end": "(\\\\end\\{minted\\})" + }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:css)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" } ] } @@ -198,12 +217,31 @@ "end": "(\\\\end\\{minted\\})" }, { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{html\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:hs|haskell)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.haskell", + "patterns": [ + { + "include": "source.haskell" + } + ], + "end": "(\\\\end\\{minted\\})" + }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:html)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" } ] } @@ -217,12 +255,31 @@ "end": "(\\\\end\\{minted\\})" }, { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{java\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:xml)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "text.xml", + "patterns": [ + { + "include": "text.xml" + } + ], + "end": "(\\\\end\\{minted\\})" + }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:java)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" } ] } @@ -235,13 +292,70 @@ ], "end": "(\\\\end\\{minted\\})" }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:lua)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.lua", + "patterns": [ + { + "include": "source.lua" + } + ], + "end": "(\\\\end\\{minted\\})" + }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:jl|julia)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ], + "end": "(\\\\end\\{minted\\})" + }, + { + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:rb|ruby)\\})", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.ruby", + "patterns": [ + { + "include": "source.ruby" + } + ], + "end": "(\\\\end\\{minted\\})" + }, { "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:js|javascript)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -260,7 +374,7 @@ "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -274,31 +388,12 @@ "end": "(\\\\end\\{minted\\})" }, { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{lua\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:py|python)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" - } - ] - } - }, - "contentName": "source.lua", - "patterns": [ - { - "include": "source.lua" - } - ], - "end": "(\\\\end\\{minted\\})" - }, - { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:python|py)\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -312,69 +407,12 @@ "end": "(\\\\end\\{minted\\})" }, { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(julia)\\})", + "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(?:yaml)\\})", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" - } - ] - } - }, - "contentName": "source.julia", - "patterns": [ - { - "include": "source.julia" - } - ], - "end": "(\\\\end\\{minted\\})" - }, - { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{(ruby)\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#env-mandatory-arg" - } - ] - } - }, - "contentName": "source.ruby", - "patterns": [ - { - "include": "source.ruby" - } - ], - "end": "(\\\\end\\{minted\\})" - }, - { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{xml\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#env-mandatory-arg" - } - ] - } - }, - "contentName": "text.xml", - "patterns": [ - { - "include": "text.xml" - } - ], - "end": "(\\\\end\\{minted\\})" - }, - { - "begin": "(\\\\begin\\{minted\\}(?:\\[.*\\])?\\{yaml\\})", - "captures": { - "1": { - "patterns": [ - { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -393,7 +431,7 @@ "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -405,12 +443,12 @@ ] }, { - "begin": "((?:\\s*)\\\\begin\\{(cppcode(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:cppcode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -419,20 +457,17 @@ "patterns": [ { "include": "source.cpp.embedded.latex" - }, - { - "include": "source.cpp" } ], "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{(hscode(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:hscode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -446,12 +481,12 @@ "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{(luacode(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:luacode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -465,12 +500,12 @@ "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{((?:julia|jl)(?:code|verbatim|block|concode|console|converbatim)(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -484,12 +519,31 @@ "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{((?:(?:(?:py|pylab|sympy)(?:code|verbatim|block|concode|console|converbatim))|sageblock|sagesilent|sageverbatim|sageexample|sagecommandline)(?:\\*)?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "begin": "(\\s*\\\\begin\\{((?:juliacode|juliaverbatim|juliablock|juliaconcode|juliaconsole|juliaconverbatim)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.julia", + "patterns": [ + { + "include": "source.julia" + } + ], + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" + }, + { + "begin": "(\\s*\\\\begin\\{((?:sageblock|sagesilent|sageverbatim|sageexample|sagecommandline|python|pythonq|pythonrepl)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" } ] } @@ -503,12 +557,69 @@ "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{(scalacode(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:pycode|pyverbatim|pyblock|pyconcode|pyconsole|pyconverbatim)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ], + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" + }, + { + "begin": "(\\s*\\\\begin\\{((?:pylabcode|pylabverbatim|pylabblock|pylabconcode|pylabconsole|pylabconverbatim)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ], + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" + }, + { + "begin": "(\\s*\\\\begin\\{((?:sympycode|sympyverbatim|sympyblock|sympyconcode|sympyconsole|sympyconverbatim)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "contentName": "source.python", + "patterns": [ + { + "include": "source.python" + } + ], + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" + }, + { + "begin": "(\\s*\\\\begin\\{((?:scalacode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" } ] } @@ -522,25 +633,12 @@ "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{([a-z]*code(?:\\*)?)\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:asy|asycode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" - } - ] - } - }, - "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" - }, - { - "begin": "((?:\\s*)\\\\begin\\{asy\\}(?:\\[.*\\])?)", - "captures": { - "1": { - "patterns": [ - { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -551,15 +649,15 @@ "include": "source.asymptote" } ], - "end": "(\\\\end\\{asy\\}(?:\\s*\\n)?)" + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{dot2tex\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:dot2tex|dotcode)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -570,15 +668,15 @@ "include": "source.dot" } ], - "end": "(\\\\end\\{dot2tex\\}(?:\\s*\\n)?)" + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { - "begin": "((?:\\s*)\\\\begin\\{gnuplot\\}(?:\\[.*\\])?)", + "begin": "(\\s*\\\\begin\\{((?:gnuplot)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", "captures": { "1": { "patterns": [ { - "include": "#code-env" + "include": "#begin-env-tokenizer" } ] } @@ -589,7 +687,20 @@ "include": "source.gnuplot" } ], - "end": "(\\\\end\\{gnuplot\\}(?:\\s*\\n)?)" + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" + }, + { + "begin": "((?:\\s*)\\\\begin\\{([a-z]*code(?:\\*)?)\\}(?:\\[.*\\])?(?:\\{.*\\})?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)" }, { "begin": "((\\\\)addplot)(?:\\+?)((?:\\[[^\\[]*\\]))*\\s*(gnuplot)\\s*((?:\\[[^\\[]*\\]))*\\s*(\\{)", @@ -639,81 +750,48 @@ "end": "\\s*(\\};)" }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:fboxv|boxedv|V|v|spv)erbatim\\*?)(\\})", + "begin": "(\\s*\\\\begin\\{((?:fboxv|boxedv|V|v|spv)erbatim\\*?)\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, "contentName": "markup.raw.verbatim.latex", - "end": "((\\\\)end)(\\{)(\\4)(\\})", + "end": "(\\\\end\\{\\2\\})", "name": "meta.function.verbatim.latex" }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(VerbatimOut)(\\})(\\{)([^\\}]*)(\\})", + "begin": "(\\s*\\\\begin\\{VerbatimOut\\}\\{[^\\}]*\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" - }, - "6": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "7": { - "name": "support.class.latex" - }, - "8": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, "contentName": "markup.raw.verbatim.latex", - "end": "((\\\\)end)(\\{)(\\VerbatimOut)(\\})", + "end": "(\\\\end\\{\\VerbatimOut\\})", "name": "meta.function.verbatim.latex" }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(alltt)(\\})", + "begin": "(\\s*\\\\begin\\{alltt\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, "contentName": "markup.raw.verbatim.latex", - "end": "((\\\\)end)(\\{)(alltt)(\\})", + "end": "(\\\\end\\{alltt\\})", "name": "meta.function.alltt.latex", "patterns": [ { @@ -728,26 +806,18 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:C|c)omment)(\\})", + "begin": "(\\s*\\\\begin\\{([Cc]omment)\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, "contentName": "punctuation.definition.comment.latex", - "end": "((\\\\)end)(\\{)(\\4)(\\})", + "end": "(\\\\end\\{\\2\\})", "name": "meta.function.verbatim.latex" }, { @@ -772,50 +842,34 @@ "name": "meta.function.link.url.latex" }, { - "captures": { - "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" - } - }, "comment": "These two patterns match the \\begin{document} and \\end{document} commands, so that the environment matching pattern following them will ignore those commands.", - "match": "(?:\\s*)((\\\\)begin)(\\{)(document)(\\})", - "name": "meta.function.begin-document.latex" - }, - { + "match": "(\\s*\\\\begin\\{document\\})", + "name": "meta.function.begin-document.latex", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } - }, - "match": "(?:\\s*)((\\\\)end)(\\{)(document)(\\})", - "name": "meta.function.end-document.latex" + } }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:array|equation|(?:IEEE)?eqnarray|multline|align|aligned|alignat|alignedat|flalign|flaligned|flalignat|split|gather|gathered|cases|(?:display)?math|[a-zA-Z]*matrix|(?:(?:arg)?(?:mini|maxi)))(?:\\*|!)?)(\\})(\\s*\\n)?", + "match": "(\\s*\\\\end\\{document\\})", + "name": "meta.function.end-document.latex", + "captures": { + "1": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + } + }, + { + "begin": "(?:\\s*)((\\\\)begin)(\\{)((?:array|equation|(?:IEEE)?eqnarray|multline|align|aligned|alignat|alignedat|flalign|flaligned|flalignat|split|gather|gathered|cases|(?:display)?math|[a-zA-Z]*matrix|[pbBvV]?NiceMatrix|[pbBvV]?NiceArray|(?:(?:arg)?(?:mini|maxi)))(?:\\*|!)?)(\\})(\\s*\\n)?", "captures": { "1": { "name": "support.function.be.latex" @@ -862,7 +916,7 @@ "1": { "patterns": [ { - "include": "#env-mandatory-arg" + "include": "#begin-env-tokenizer" } ] } @@ -891,26 +945,18 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(tabular[xy*]?|xltabular|longtable|(?:long)?tabu|(?:long|tall)?tblr|NiceTabular[X*]?)(\\})(\\s*\\n)?", + "begin": "(\\s*\\\\begin\\{(tabular[xy*]?|xltabular|longtable|(?:long)?tabu|(?:long|tall)?tblr|NiceTabular[X*]?)\\}(\\s*\\n)?)", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, "contentName": "meta.data.environment.tabular.latex", - "end": "(?:\\s*)((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?", + "end": "(\\s*\\\\end\\{(\\2)\\}(?:\\s*\\n)?)", "name": "meta.function.environment.tabular.latex", "patterns": [ { @@ -927,25 +973,17 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(itemize|enumerate|description|list)(\\})", + "begin": "(\\s*\\\\begin\\{(itemize|enumerate|description|list)\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?", + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)", "name": "meta.function.environment.list.latex", "patterns": [ { @@ -954,25 +992,17 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(tikzpicture)(\\})", + "begin": "(\\s*\\\\begin\\{tikzpicture\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(tikzpicture)(\\})(?:\\s*\\n)?", + "end": "(\\\\end\\{tikzpicture\\}(?:\\s*\\n)?)", "name": "meta.function.environment.latex.tikz", "patterns": [ { @@ -981,25 +1011,17 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(frame)(\\})", + "begin": "(\\s*\\\\begin\\{frame\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(frame)(\\})", + "end": "(\\\\end\\{frame\\})", "name": "meta.function.environment.frame.latex", "patterns": [ { @@ -1008,47 +1030,31 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(mpost[*]?)(\\})", + "begin": "(\\s*\\\\begin\\{(mpost\\*?)\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?", + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)", "name": "meta.function.environment.latex.mpost" }, { - "begin": "(?:\\s*)?((\\\\)begin(\\{)(markdown)\\})", + "begin": "(\\s*\\\\begin\\{markdown\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(markdown)(\\})", + "end": "(\\\\end\\{markdown\\})", "contentName": "meta.embedded.markdown_latex_combined", "patterns": [ { @@ -1057,25 +1063,17 @@ ] }, { - "begin": "(?:\\s*)((\\\\)begin)(\\{)(\\w+[*]?)(\\})", + "begin": "(\\s*\\\\begin\\{(\\w+\\*?)\\})", "captures": { "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] } }, - "end": "((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?", + "end": "(\\\\end\\{\\2\\}(?:\\s*\\n)?)", "name": "meta.function.environment.general.latex", "patterns": [ { @@ -1396,7 +1394,7 @@ ] }, { - "begin": "((\\\\)(?:\\w*[r|R]ef\\*?))(\\{)", + "begin": "((\\\\)(?:\\w*[rR]ef\\*?))(\\{)", "beginCaptures": { "1": { "name": "keyword.control.ref.latex" @@ -1603,7 +1601,7 @@ "name": "punctuation.definition.verb.latex" } }, - "match": "((\\\\)(?:py|pycon|pylab|pylabcon|sympy|sympycon)[cv]?)((?:\\[[^\\[]*?\\])?)(?:(?:([^a-zA-Z\\{])(.*?)(\\4))|(?:(\\{)(.*?)(\\})))", + "match": "((\\\\)(?:(?:py|pycon|pylab|pylabcon|sympy|sympycon)[cv]?|pyq|pycq|pyif))((?:\\[[^\\[]*?\\])?)(?:(?:([^a-zA-Z\\{])(.*?)(\\4))|(?:(\\{)(.*?)(\\})))", "name": "meta.function.verb.latex" }, { @@ -1839,7 +1837,7 @@ } ] }, - "env-mandatory-arg": { + "begin-env-tokenizer": { "captures": { "1": { "name": "support.function.be.latex" @@ -1879,33 +1877,7 @@ "name": "punctuation.definition.arguments.end.latex" } }, - "match": "((\\\\)(?:begin|end))(\\{)([a-z]*)(\\})(?:(\\[)(.*)(\\]))?(?:(\\{)([^{}]*)(\\}))?" - }, - "code-env": { - "captures": { - "1": { - "name": "support.function.be.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.arguments.begin.latex" - }, - "4": { - "name": "variable.parameter.function.latex" - }, - "5": { - "name": "punctuation.definition.arguments.end.latex" - }, - "6": { - "name": "punctuation.definition.arguments.optional.begin.latex" - }, - "7": { - "name": "punctuation.definition.arguments.optional.end.latex" - } - }, - "match": "(?:\\s*)((\\\\)(?:begin|end))(\\{)([a-z]+(?:\\*)?)(\\})(?:(\\[).*(\\]))?" + "match": "\\s*((\\\\)(?:begin|end))(\\{)([a-zA-Z]*\\*?)(\\})(?:(\\[)(.*)(\\]))?(?:(\\{)([^{}]*)(\\}))?" }, "definition-label": { "begin": "((\\\\)label)((?:\\[[^\\[]*?\\])*)(\\{)", diff --git a/extensions/latex/syntaxes/TeX.tmLanguage.json b/extensions/latex/syntaxes/TeX.tmLanguage.json index ff494480295..4e3e4d697b5 100644 --- a/extensions/latex/syntaxes/TeX.tmLanguage.json +++ b/extensions/latex/syntaxes/TeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/b98c2d4911652824fc990f4b26c9c30be59b78a2", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/8776a0856846b63d9e5765e8ec42a8a2f4f52219", "name": "TeX", "scopeName": "text.tex", "patterns": [ @@ -167,7 +167,7 @@ "name": "punctuation.math.bracket.pair.tex" }, { - "match": "\\\\(left|right|((big|bigg|Big|Bigg)[lr]?))([\\(\\[\\<\\>\\]\\)\\.\\|]|\\\\[{}|]|\\\\[lr]?[Vv]ert|\\\\[lr]angle|\\\\\\|)", + "match": "\\\\(left|right|((big|bigg|Big|Bigg)[lr]?))([\\(\\[\\<\\>\\]\\)\\.\\|]|\\\\[{}|]|\\\\[lr]?[Vv]ert|\\\\[lr]angle)", "name": "punctuation.math.bracket.pair.big.tex" }, { diff --git a/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json b/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json index bf084fd2fd2..a303594f0ae 100644 --- a/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json +++ b/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/db888fc191f6b5610cd6866cc49017fc3dfb00b5", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/ffe263692d6096b24e4897650e933c587fa9c55f", "name": "C++", "scopeName": "source.cpp.embedded.latex", "patterns": [ @@ -573,7 +573,7 @@ "name": "comment.block.cpp" }, "builtin_storage_type_initilizer": { - "begin": "(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:consteval)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -1804,7 +1801,7 @@ ] }, "constructor_root": { - "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "\\s*+((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -2179,7 +2176,7 @@ ] }, "control_flow_keywords": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\{)", "end": "\\}|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -3126,14 +3123,11 @@ "patterns": [ { "include": "#evaluation_context" - }, - { - "include": "#c_conditional_context" } ] }, "destructor_inline": { - "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:constexpr)|(?:consteval)|(?:explicit)|(?:mutable)|(?:virtual)|(?:inline)|(?:friend))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(~(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -3369,7 +3363,7 @@ ] }, "destructor_root": { - "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", + "begin": "((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)(((?>(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))::((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))~\\14((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\())", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -3661,7 +3655,7 @@ }, "diagnostic": { "begin": "(^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?((?:error|warning)))\\b(?:(?:\\s)+)?", - "end": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?(::))?(?:(?:\\s)+)?((?|\\?\\?>)(?:(?:\\s)+)?(;)|(;))|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -4525,7 +4519,7 @@ ] }, "function_call": { - "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?(\\()", "end": "\\)|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -4599,7 +4593,7 @@ ] }, "function_definition": { - "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", + "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<60>?)+>)(?:\\s)*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -4671,7 +4665,7 @@ "11": { "patterns": [ { - "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", + "match": "((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))", "captures": { "1": { "name": "storage.modifier.$1.cpp" @@ -5162,7 +5156,7 @@ ] }, { - "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", + "match": "(?<=^|\\))(?:(?:\\s)+)?(->)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<23>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "punctuation.definition.function.return-type.cpp" @@ -5410,7 +5404,7 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -5761,7 +5755,7 @@ ] }, "function_pointer_parameter": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -6112,7 +6106,7 @@ ] }, "functional_specifiers_pre_parameters": { - "match": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", + "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)", "captures": { "1": { - "name": "keyword.control.goto.cpp" - }, - "2": { "patterns": [ { "include": "#inline_comment" } ] }, - "3": { + "2": { "patterns": [ { "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", @@ -6219,7 +6210,44 @@ } ] }, + "3": { + "name": "keyword.control.goto.cpp" + }, "4": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "5": { + "patterns": [ + { + "match": "(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + } + } + ] + }, + "6": { "name": "entity.name.label.call.cpp" } } @@ -6482,7 +6510,7 @@ "name": "storage.type.modifier.virtual.cpp" }, { - "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:(?:\\s)+)?(?!(?:(?:(?:protected)|(?:private)|(?:public))|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "meta.qualified_type.cpp", @@ -6680,7 +6708,7 @@ ] }, "inline_builtin_storage_type": { - "match": "(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?line\\b", - "end": "(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?define\\b)(?:(?:\\s)+)?((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_least8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int_fast8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|suseconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|useconds_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_addr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|in_port_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uintptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|blksize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intmax_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|unsigned[^Pattern.new(\n match: \\/\\w\\/,\n)]|blkcnt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|intptr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|swblk_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|wchar_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_short[^Pattern.new(\n match: \\/\\w\\/,\n)]|qaddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|caddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|daddr_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|fixpt_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|nlink_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|segsz_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|clock_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ssize_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int16_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int32_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int64_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int8_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|mode_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|quad_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ushort[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_long[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_char[^Pattern.new(\n match: \\/\\w\\/,\n)]|double[^Pattern.new(\n match: \\/\\w\\/,\n)]|signed[^Pattern.new(\n match: \\/\\w\\/,\n)]|time_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|size_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|key_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|div_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|ino_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|gid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|off_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|pid_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|float[^Pattern.new(\n match: \\/\\w\\/,\n)]|dev_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|u_int[^Pattern.new(\n match: \\/\\w\\/,\n)]|short[^Pattern.new(\n match: \\/\\w\\/,\n)]|bool[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|uint[^Pattern.new(\n match: \\/\\w\\/,\n)]|long[^Pattern.new(\n match: \\/\\w\\/,\n)]|char[^Pattern.new(\n match: \\/\\w\\/,\n)]|void[^Pattern.new(\n match: \\/\\w\\/,\n)]|auto[^Pattern.new(\n match: \\/\\w\\/,\n)]|id_t[^Pattern.new(\n match: \\/\\w\\/,\n)]|int[^Pattern.new(\n match: \\/\\w\\/,\n)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", + "match": "(?:((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:(?:\\s)+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:(?:\\s)+)?)*)(?:(?:\\s)+)?(\\b(?!uint_least16_t[^\\w]|uint_least32_t[^\\w]|uint_least64_t[^\\w]|int_least16_t[^\\w]|int_least32_t[^\\w]|int_least64_t[^\\w]|uint_least8_t[^\\w]|uint_fast16_t[^\\w]|uint_fast32_t[^\\w]|uint_fast64_t[^\\w]|int_least8_t[^\\w]|int_fast16_t[^\\w]|int_fast32_t[^\\w]|int_fast64_t[^\\w]|uint_fast8_t[^\\w]|suseconds_t[^\\w]|int_fast8_t[^\\w]|useconds_t[^\\w]|blksize_t[^\\w]|in_addr_t[^\\w]|in_port_t[^\\w]|uintptr_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|unsigned[^\\w]|u_quad_t[^\\w]|blkcnt_t[^\\w]|uint16_t[^\\w]|uint32_t[^\\w]|uint64_t[^\\w]|intptr_t[^\\w]|intmax_t[^\\w]|intmax_t[^\\w]|wchar_t[^\\w]|u_short[^\\w]|qaddr_t[^\\w]|caddr_t[^\\w]|daddr_t[^\\w]|fixpt_t[^\\w]|nlink_t[^\\w]|segsz_t[^\\w]|swblk_t[^\\w]|clock_t[^\\w]|ssize_t[^\\w]|int16_t[^\\w]|int32_t[^\\w]|int64_t[^\\w]|uint8_t[^\\w]|signed[^\\w]|double[^\\w]|u_char[^\\w]|u_long[^\\w]|ushort[^\\w]|quad_t[^\\w]|mode_t[^\\w]|size_t[^\\w]|time_t[^\\w]|int8_t[^\\w]|short[^\\w]|float[^\\w]|u_int[^\\w]|div_t[^\\w]|dev_t[^\\w]|gid_t[^\\w]|ino_t[^\\w]|key_t[^\\w]|pid_t[^\\w]|off_t[^\\w]|uid_t[^\\w]|auto[^\\w]|void[^\\w]|char[^\\w]|long[^\\w]|bool[^\\w]|uint[^\\w]|id_t[^\\w]|id_t[^\\w]|int[^\\w])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", "captures": { "1": { "patterns": [ @@ -7474,7 +7503,7 @@ ] }, "misc_keywords": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<8>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<4>?)+>)(?:\\s)*+)?::)*\\s*+)(?:(?:\\s)+)?((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", + "begin": "(?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(operator)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<55>?)+>)(?:\\s)*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:new)|(?:\\->\\*)|(?:<<=)|(?:>>=)|(?:<=>)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:\\-\\-)|(?:<<)|(?:>>)|(?:<=)|(?:>=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|(?:\\/=)|(?:%=)|(?:&=)|(?:\\^=)|(?:\\|=)|(?:\\+)|(?:\\-)|!|~|(?:\\*)|&|(?:\\*)|(?:\\/)|%|(?:\\+)|(?:\\-)|<|>|&|(?:\\^)|(?:\\|)|=|,))|((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=\\<|\\()", "end": "(?:(?<=\\}|%>|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -9062,14 +9091,14 @@ }, { "match": "&|\\||\\^|~", - "name": "keyword.operator.cpp" + "name": "keyword.operator.bitwise.cpp" }, { "include": "#assignment_operator" }, { "match": "%|\\*|\\/|-|\\+", - "name": "keyword.operator.cpp" + "name": "keyword.operator.arithmetic.cpp" }, { "include": "#ternary_operator" @@ -9079,7 +9108,7 @@ "over_qualified_types": { "patterns": [ { - "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bstruct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.struct.parameter.cpp" @@ -9408,7 +9437,7 @@ } }, { - "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\benum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.enum.parameter.cpp" @@ -9737,7 +9766,7 @@ } }, { - "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bunion)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.union.parameter.cpp" @@ -10066,7 +10095,7 @@ } }, { - "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bclass)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.class.parameter.cpp" @@ -10446,7 +10475,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -10768,7 +10797,7 @@ ] }, "parameter_class": { - "match": "(class)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bclass)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.class.parameter.cpp" @@ -11097,7 +11126,7 @@ } }, "parameter_enum": { - "match": "(enum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\benum)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.enum.parameter.cpp" @@ -11484,7 +11513,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", + "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))+)((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:\\s)*+(?(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=,|\\)|=)", "captures": { "1": { "patterns": [ @@ -11808,7 +11837,7 @@ ] }, "parameter_struct": { - "match": "(struct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bstruct)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.struct.parameter.cpp" @@ -12137,7 +12166,7 @@ } }, "parameter_union": { - "match": "(union)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", + "match": "(\\bunion)((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?((?:(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:\\[((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))\\]((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?=,|\\)|\\n)", "captures": { "1": { "name": "storage.type.union.parameter.cpp" @@ -12494,7 +12523,7 @@ }, "pragma": { "begin": "^((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(#)(?:(?:\\s)+)?pragma\\b", - "end": "(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<11>?)+>)?(?![\\w<:.])", "captures": { "0": { "patterns": [ @@ -13089,7 +13118,7 @@ } }, "scope_resolution": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13111,7 +13140,7 @@ } }, "scope_resolution_function_call": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13133,7 +13162,7 @@ } }, "scope_resolution_function_call_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13171,7 +13200,7 @@ } }, "scope_resolution_function_definition": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13193,7 +13222,7 @@ } }, "scope_resolution_function_definition_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13231,7 +13260,7 @@ } }, "scope_resolution_function_definition_operator_overload": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13253,7 +13282,7 @@ } }, "scope_resolution_function_definition_operator_overload_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13291,7 +13320,7 @@ } }, "scope_resolution_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13329,7 +13358,7 @@ } }, "scope_resolution_namespace_alias": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13351,7 +13380,7 @@ } }, "scope_resolution_namespace_alias_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13389,7 +13418,7 @@ } }, "scope_resolution_namespace_block": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13411,7 +13440,7 @@ } }, "scope_resolution_namespace_block_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13449,7 +13478,7 @@ } }, "scope_resolution_namespace_using": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13471,7 +13500,7 @@ } }, "scope_resolution_namespace_using_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13509,7 +13538,7 @@ } }, "scope_resolution_parameter": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13531,7 +13560,7 @@ } }, "scope_resolution_parameter_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13569,7 +13598,7 @@ } }, "scope_resolution_template_call": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13591,7 +13620,7 @@ } }, "scope_resolution_template_call_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13629,7 +13658,7 @@ } }, "scope_resolution_template_definition": { - "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<3>?)+>)(?:\\s)*+)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -13651,7 +13680,7 @@ } }, "scope_resolution_template_definition_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<7>?)+>)(?:\\s)*+)?(::)", "captures": { "1": { "patterns": [ @@ -13693,7 +13722,7 @@ "name": "punctuation.terminator.statement.cpp" }, "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<12>?)+>)?(?![\\w<:.]))(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?", "captures": { "1": { "name": "meta.qualified_type.cpp", @@ -15207,7 +15236,7 @@ } }, "storage_specifiers": { - "match": "((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))((?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", + "match": "(using)(?:(?:\\s)+)?(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))(?:(?:\\s)+)?(\\=)(?:(?:\\s)+)?((?:typename)?)(?:(?:\\s)+)?((?:(?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<29>?)+>)?(?![\\w<:.]))|(.*(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)?(?:(?:\\s)+)?(?:(;)|\\n)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -17593,7 +17616,7 @@ "endCaptures": {}, "patterns": [ { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)(?:\\s)*+)?::)*+)?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<18>?)+>)?(?![\\w<:.]))(((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))?(?:(?:&|(?:\\*))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*(?:&|(?:\\*)))?((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\()(\\*)(?:(?:\\s)+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:(?:\\s)+)?(?:(\\[)(\\w*)(\\])(?:(?:\\s)+)?)*(\\))(?:(?:\\s)+)?(\\()", "end": "(\\))((?:(?:(?:(?>(?:\\s)+)|(\\/\\*)((?:[^\\*]|(?:\\*)++[^\\/])*+((?:\\*)++\\/)))+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?=[{=,);>]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -18878,7 +18901,7 @@ ] }, "typename": { - "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|synchronized|dynamic_cast|thread_local|static_cast|const_cast|co_return|constexpr|constexpr|constexpr|co_return|protected|namespace|consteval|noexcept|decltype|template|operator|noexcept|co_yield|co_await|reflexpr|continue|co_await|co_yield|requires|volatile|register|restrict|explicit|volatile|noexcept|typename|default|_Pragma|mutable|include|concept|alignas|virtual|alignof|__asm__|defined|mutable|typedef|warning|private|and_eq|define|pragma|typeid|switch|bitand|return|ifndef|export|struct|sizeof|module|static|public|extern|inline|friend|delete|xor_eq|import|not_eq|class|compl|bitor|throw|or_eq|while|catch|break|union|const|const|endif|ifdef|undef|error|using|else|line|goto|else|elif|this|enum|case|new|asm|not|try|for|and|xor|or|if|do|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:atomic_cancel)|(?:__has_include)|(?:dynamic_cast)|(?:synchronized)|(?:thread_local)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:consteval)|(?:co_return)|(?:co_return)|(?:constexpr)|(?:protected)|(?:constexpr)|(?:namespace)|(?:noexcept)|(?:typename)|(?:decltype)|(?:template)|(?:operator)|(?:noexcept)|(?:co_yield)|(?:co_await)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:reflexpr)|(?:noexcept)|(?:requires)|(?:alignas)|(?:typedef)|(?:nullptr)|(?:alignof)|(?:mutable)|(?:concept)|(?:virtual)|(?:defined)|(?:__asm__)|(?:include)|(?:_Pragma)|(?:mutable)|(?:default)|(?:warning)|(?:private)|(?:module)|(?:return)|(?:not_eq)|(?:xor_eq)|(?:and_eq)|(?:ifndef)|(?:pragma)|(?:export)|(?:import)|(?:sizeof)|(?:static)|(?:delete)|(?:public)|(?:define)|(?:extern)|(?:inline)|(?:typeid)|(?:switch)|(?:friend)|(?:bitand)|(?:false)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:line)|(?:else)|(?:elif)|(?:true)|(?:NULL)|(?:case)|(?:goto)|(?:else)|(?:this)|(?:new)|(?:asm)|(?:not)|(?:and)|(?:xor)|(?:try)|(?:for)|(?:if)|(?:do)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", + "match": "(((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z))))*((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|__has_include|atomic_cancel|atomic_commit|dynamic_cast|thread_local|synchronized|static_cast|const_cast|co_return|constexpr|consteval|constexpr|constexpr|consteval|protected|namespace|constinit|co_return|noexcept|noexcept|continue|co_await|co_yield|volatile|register|restrict|explicit|volatile|noexcept|template|operator|decltype|typename|requires|co_await|co_yield|reflexpr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|public|ifndef|define|pragma|export|import|module|compl|bitor|throw|or_eq|while|catch|break|class|union|const|const|endif|ifdef|undef|error|using|else|goto|case|enum|elif|else|line|this|not|new|xor|and|for|try|asm|or|do|if|if)\\b)(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)(?:\\s)*+)?::)*+)?((?:((?:(?>(?:\\s)+)|\\/\\*(?:[^\\*]|(?:\\*)++[^\\/])*+(?:\\*)++\\/)+)|(?:\\b)|(?=\\W)|(?<=\\W)|(?:\\A)|(?:\\Z)))(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:__has_include)|(?:atomic_cancel)|(?:atomic_commit)|(?:dynamic_cast)|(?:thread_local)|(?:synchronized)|(?:static_cast)|(?:const_cast)|(?:co_return)|(?:constexpr)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:consteval)|(?:protected)|(?:namespace)|(?:constinit)|(?:co_return)|(?:noexcept)|(?:noexcept)|(?:continue)|(?:co_await)|(?:co_yield)|(?:volatile)|(?:register)|(?:restrict)|(?:explicit)|(?:override)|(?:volatile)|(?:noexcept)|(?:template)|(?:operator)|(?:decltype)|(?:typename)|(?:requires)|(?:co_await)|(?:co_yield)|(?:reflexpr)|(?:alignof)|(?:alignas)|(?:default)|(?:nullptr)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:sizeof)|(?:delete)|(?:not_eq)|(?:bitand)|(?:and_eq)|(?:xor_eq)|(?:typeid)|(?:switch)|(?:return)|(?:static)|(?:extern)|(?:inline)|(?:friend)|(?:public)|(?:ifndef)|(?:define)|(?:pragma)|(?:export)|(?:import)|(?:module)|(?:compl)|(?:bitor)|(?:throw)|(?:or_eq)|(?:while)|(?:catch)|(?:break)|(?:false)|(?:const)|(?:final)|(?:const)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:using)|(?:audit)|(?:axiom)|(?:else)|(?:goto)|(?:case)|(?:NULL)|(?:true)|(?:elif)|(?:else)|(?:line)|(?:this)|(?:not)|(?:new)|(?:xor)|(?:and)|(?:for)|(?:try)|(?:asm)|(?:or)|(?:do)|(?:if)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<17>?)+>)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -19767,7 +19790,7 @@ } }, "using_namespace": { - "begin": "(?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?]*+|\"(?:[^\"]*|\\\\\")\")|'(?:[^']*|\\\\')')\\g<6>?)+>)(?:\\s)*+)?::)*\\s*+)?((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n ((?>[^\\s()]+)|\\(\\g*\\))*)(>?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parensâ€Ļ\n | ((\").+?(\")) # or in double quotesâ€Ļ\n | ((').+?(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", + "match": "(?x)\n (\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n # The url\n [ \\t]*\n (\n (<)([^<>\\n]*)(>)\n | ((?(?>[^\\s()]+)|\\(\\g*\\))*)\n )\n [ \\t]*\n # The title \n (?:\n ((\\()[^()]*(\\))) # Match title in parensâ€Ļ\n | ((\")[^\"]*(\")) # or in double quotesâ€Ļ\n | ((')[^']*(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n", "name": "meta.link.inline.markdown" }, "link-ref": { @@ -2808,7 +2847,7 @@ "name": "punctuation.definition.constant.end.markdown" } }, - "match": "(\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])", + "match": "(?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])", "name": "meta.link.reference.markdown" }, "link-ref-literal": { @@ -2829,7 +2868,7 @@ "name": "punctuation.definition.constant.end.markdown" } }, - "match": "(\\[)((?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])[ ]?(\\[)(\\])", + "match": "(?[^\\[\\]\\\\]|\\\\.|\\[\\g*+\\])*+)(\\])[ ]?(\\[)(\\])", "name": "meta.link.reference.literal.markdown" }, "link-ref-shortcut": { @@ -2844,7 +2883,7 @@ "name": "punctuation.definition.link.title.end.markdown" } }, - "match": "(\\[)(\\S+?)(\\])", + "match": "(? = (ctx) => { code { font-size: 1em; + font-family: var(--vscode-editor-font-family); } pre code { - font-family: var(--vscode-editor-font-family); - line-height: 1.357em; white-space: pre-wrap; } diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 547983e3a11..1de8e59e74a 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -6,7 +6,7 @@ "icon": "icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.20.0" }, @@ -16,7 +16,6 @@ "Programming Languages" ], "enabledApiProposals": [ - "textEditorDrop", "documentPaste" ], "activationEvents": [ @@ -47,7 +46,7 @@ "contributes": { "notebookRenderer": [ { - "id": "markdownItRenderer", + "id": "vscode.markdown-it-renderer", "displayName": "Markdown it renderer", "entrypoint": "./notebook-out/index.js", "mimeTypes": [ @@ -188,22 +187,22 @@ }, { "command": "markdown.showSource", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "navigation" }, { "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" }, { "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" }, { "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" } ], @@ -248,7 +247,7 @@ }, { "command": "markdown.showSource", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "navigation" }, { @@ -257,11 +256,11 @@ }, { "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.preview.refresh", @@ -269,7 +268,7 @@ }, { "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.findAllFileReferences", @@ -399,16 +398,27 @@ "description": "%configuration.markdown.suggest.paths.enabled.description%", "scope": "resource" }, - "markdown.trace": { + "markdown.trace.extension": { "type": "string", "enum": [ "off", "verbose" ], "default": "off", - "description": "%markdown.trace.desc%", + "description": "%markdown.trace.extension.desc%", "scope": "window" }, + "markdown.trace.server": { + "type": "string", + "scope": "window", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "%markdown.trace.server.desc%" + }, "markdown.editor.drop.enabled": { "type": "boolean", "default": true, @@ -419,7 +429,7 @@ "type": "boolean", "scope": "resource", "markdownDescription": "%configuration.markdown.editor.pasteLinks.enabled%", - "default": false, + "default": true, "tags": [ "experimental" ] @@ -479,6 +489,7 @@ "type": "string", "scope": "resource", "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%", + "default": "ignore", "enum": [ "ignore", "warning", @@ -498,13 +509,54 @@ "tags": [ "experimental" ] + }, + "markdown.experimental.updateLinksOnFileMove.enabled": { + "type": "string", + "enum": [ + "prompt", + "always", + "never" + ], + "markdownEnumDescriptions": [ + "%configuration.markdown.experimental.updateLinksOnFileMove.enabled.prompt%", + "%configuration.markdown.experimental.updateLinksOnFileMove.enabled.always%", + "%configuration.markdown.experimental.updateLinksOnFileMove.enabled.never%" + ], + "default": "never", + "markdownDescription": "%configuration.markdown.experimental.updateLinksOnFileMove.enabled%", + "scope": "resource", + "tags": [ + "experimental" + ] + }, + "markdown.experimental.updateLinksOnFileMove.externalFileGlobs": { + "type": "string", + "default": "**/*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp,avif,tiff,svg,mp4}", + "description": "%configuration.markdown.experimental.updateLinksOnFileMove.fileGlobs%", + "scope": "resource", + "tags": [ + "experimental" + ] + }, + "markdown.experimental.updateLinksOnFileMove.enableForDirectories": { + "type": "boolean", + "default": true, + "description": "%configuration.markdown.experimental.updateLinksOnFileMove.enableForDirectories%", + "scope": "resource", + "tags": [ + "experimental" + ] } } }, "configurationDefaults": { "[markdown]": { "editor.wordWrap": "on", - "editor.quickSuggestions": false + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + } } }, "jsonValidation": [ @@ -534,8 +586,8 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", + "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features-server && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", + "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService watch-extension:markdown-language-features-server", "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-notebook": "node ./esbuild-notebook", @@ -544,15 +596,16 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.4.10", + "@vscode/extension-telemetry": "0.6.2", "dompurify": "^2.3.3", "highlight.js": "^11.4.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.1", "morphdom": "^2.6.1", "picomatch": "^2.3.1", + "vscode-languageclient": "^8.0.1", "vscode-languageserver-textdocument": "^1.0.4", - "vscode-nls": "^5.0.0", + "vscode-nls": "^5.1.0", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -562,7 +615,9 @@ "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", - "lodash.throttle": "^4.1.1" + "lodash.throttle": "^4.1.1", + "vscode-languageserver-types": "^3.17.2", + "vscode-markdown-languageservice": "^0.0.0-alpha.10" }, "repository": { "type": "git", diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 808d4e77cb9..d4cea703cc2 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -17,7 +17,8 @@ "markdown.showSource.title": "Show Source", "markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the Markdown preview. Relative paths are interpreted relative to the folder open in the Explorer. If there is no open folder, they are interpreted relative to the location of the Markdown file. All '\\' need to be written as '\\\\'.", "markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings", - "markdown.trace.desc": "Enable debug logging for the Markdown extension.", + "markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.", + "markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", "markdown.findAllFileReferences": "Find File References", @@ -28,13 +29,19 @@ "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.", - "configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links.", + "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links. Requires enabling `#editor.experimental.pasteActions.enabled#`.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description": "Validate the fragment part of links to headers in other files in Markdown files, e.g. `[link](/path/to/file.md#header)`. Inherits the setting value from `#markdown.experimental.validate.fragmentLinks.enabled#` by default.", "configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.", + "configuration.markdown.experimental.updateLinksOnFileMove.enabled": "Try to update links in Markdown files when a file is renamed/moved in the workspace. Use `#markdown.experimental.updateLinksOnFileMove.externalFileGlobs#` to configure which files trigger link updates.", + "configuration.markdown.experimental.updateLinksOnFileMove.enabled.prompt": "Prompt on each file move.", + "configuration.markdown.experimental.updateLinksOnFileMove.enabled.always": "Always update links automatically.", + "configuration.markdown.experimental.updateLinksOnFileMove.enabled.never": "Never try to update link and don't prompt.", + "configuration.markdown.experimental.updateLinksOnFileMove.fileGlobs": "A glob that specifies which files besides markdown should trigger a link update.", + "configuration.markdown.experimental.updateLinksOnFileMove.enableForDirectories": "enable/disable updating links when a directory is moved or renamed in the workspace.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/preview-src/loading.ts b/extensions/markdown-language-features/preview-src/loading.ts index fea2d653d4d..c6439188766 100644 --- a/extensions/markdown-language-features/preview-src/loading.ts +++ b/extensions/markdown-language-features/preview-src/loading.ts @@ -29,9 +29,7 @@ export class StyleLoadingMonitor { return; } this.finishedLoading = true; - if (this.poster) { - this.poster.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles }); - } + this.poster?.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles }); }); } @@ -41,4 +39,4 @@ export class StyleLoadingMonitor { poster.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles }); } } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/server/.npmignore b/extensions/markdown-language-features/server/.npmignore new file mode 100644 index 00000000000..bfd4215998c --- /dev/null +++ b/extensions/markdown-language-features/server/.npmignore @@ -0,0 +1,12 @@ +.vscode/ +.github/ +out/test/ +src/ +.eslintrc.js +.gitignore +tsconfig*.json +*.tsbuildinfo +*.map +example.cjs +CODE_OF_CONDUCT.md +SECURITY.md \ No newline at end of file diff --git a/extensions/markdown-language-features/server/.vscode/launch.json b/extensions/markdown-language-features/server/.vscode/launch.json new file mode 100644 index 00000000000..fd9033bffaa --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + "name": "Attach", + "type": "node", + "request": "attach", + "port": 7997, + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/.vscode/settings.json b/extensions/markdown-language-features/server/.vscode/settings.json new file mode 100644 index 00000000000..7a73a41bfdf --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/.vscode/tasks.json b/extensions/markdown-language-features/server/.vscode/tasks.json new file mode 100644 index 00000000000..ecc951a7baf --- /dev/null +++ b/extensions/markdown-language-features/server/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "version": "2.0.0", + "command": "npm", + "args": [ + "run", + "watch" + ], + "isBackground": true, + "problemMatcher": "$tsc-watch", + "tasks": [ + { + "label": "npm", + "type": "shell", + "command": "npm", + "args": [ + "run", + "watch" + ], + "isBackground": true, + "problemMatcher": "$tsc-watch", + "group": { + "_id": "build", + "isDefault": false + } + } + ] +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/README.md b/extensions/markdown-language-features/server/README.md new file mode 100644 index 00000000000..de4e33926c3 --- /dev/null +++ b/extensions/markdown-language-features/server/README.md @@ -0,0 +1,120 @@ +# Markdown Language Server + +> **❗ Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients. + +The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview). + +This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server. + + +## Server capabilities + +- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links. + +- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections. + +- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections. + +- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document. + +- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace + +- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable. + +- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace. + +- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace. + +- [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions. + +- (experimental) [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links. + + + +## Client requirements + +### Initialization options + +The client can send the following initialization options to the server: + +- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`. + +### Settings + +Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes. +The server supports the following settings: + +- `markdown` + - `suggest` + - `paths` + - `enabled` — Enable/disable path suggestions. + - `experimental` + - `validate` + - `enabled` — Enable/disable all validation. + - `referenceLinks` + - `enabled` — Enable/disable validation of reference links: `[text][ref]` + - `fragmentLinks` + - `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)` + - `fileLinks` + - `enabled` — Enable/disable validation of links to file in the workspace. + - `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files. + - `ignoreLinks` — Array of glob patterns for files that should not be validated. + +### Custom requests + +To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts) + +#### `markdown/parse` + +Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this. + +We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it. + +#### `markdown/fs/readFile` + +Read the contents of a file in the workspace. + +#### `markdown/fs/readDirectory` + +Read the contents of a directory in the workspace. + +#### `markdown/fs/stat` + +Check if a given file/directory exists in the workspace. + +#### `markdown/fs/watcher/create` + +Create a file watcher. This is needed for diagnostics support. + +#### `markdown/fs/watcher/delete` + +Delete a previously created file watcher. + +#### `markdown/findMarkdownFilesInWorkspace` + +Get a list of all markdown files in the workspace. + + +## Contribute + +The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server). + +File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source. + +Most of the functionality of the server is located in libraries: + +- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library. +- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS. + +Help on any of these projects is very welcome. + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License. + diff --git a/extensions/markdown-language-features/server/extension-browser.webpack.config.js b/extensions/markdown-language-features/server/extension-browser.webpack.config.js new file mode 100644 index 00000000000..b36d36f936d --- /dev/null +++ b/extensions/markdown-language-features/server/extension-browser.webpack.config.js @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../../shared.webpack.config').browser; +const path = require('path'); + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/browser/main.ts', + }, + output: { + filename: 'main.js', + path: path.join(__dirname, 'dist', 'browser'), + libraryTarget: 'var', + library: 'serverExportVar' + } +}); diff --git a/extensions/markdown-language-features/server/extension.webpack.config.js b/extensions/markdown-language-features/server/extension.webpack.config.js new file mode 100644 index 00000000000..a1917b54dc6 --- /dev/null +++ b/extensions/markdown-language-features/server/extension.webpack.config.js @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../../shared.webpack.config'); +const path = require('path'); + +module.exports = withDefaults({ + context: path.join(__dirname), + entry: { + extension: './src/node/main.ts', + }, + output: { + filename: 'main.js', + path: path.join(__dirname, 'dist', 'node'), + } +}); diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json new file mode 100644 index 00000000000..8a4a0a5286e --- /dev/null +++ b/extensions/markdown-language-features/server/package.json @@ -0,0 +1,26 @@ +{ + "name": "vscode-markdown-languageserver", + "description": "Markdown language server", + "version": "0.0.0-alpha-3", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "main": "./out/node/main", + "browser": "./dist/browser/main", + "dependencies": { + "vscode-languageserver": "^8.0.2", + "vscode-languageserver-textdocument": "^1.0.5", + "vscode-languageserver-types": "^3.17.1", + "vscode-markdown-languageservice": "^0.0.0", + "vscode-uri": "^3.0.3" + }, + "devDependencies": { + "@types/node": "16.x" + }, + "scripts": { + "compile": "gulp compile-extension:markdown-language-features-server", + "watch": "gulp watch-extension:markdown-language-features-server" + } +} diff --git a/extensions/markdown-language-features/server/src/browser/main.ts b/extensions/markdown-language-features/server/src/browser/main.ts new file mode 100644 index 00000000000..cc5e12e9c19 --- /dev/null +++ b/extensions/markdown-language-features/server/src/browser/main.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser'; +import { startServer } from '../server'; + +declare let self: any; + +const messageReader = new BrowserMessageReader(self); +const messageWriter = new BrowserMessageWriter(self); + +const connection = createConnection(messageReader, messageWriter); + +startServer(connection); diff --git a/extensions/markdown-language-features/server/src/config.ts b/extensions/markdown-language-features/server/src/config.ts new file mode 100644 index 00000000000..6f9d85fbf1d --- /dev/null +++ b/extensions/markdown-language-features/server/src/config.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LsConfiguration } from 'vscode-markdown-languageservice/out/config'; + +export { LsConfiguration }; + +const defaultConfig: LsConfiguration = { + markdownFileExtensions: ['md'], + knownLinkedToFileExtensions: [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'webp', + 'bmp', + 'tiff', + ], + excludePaths: [ + '**/.*', + '**/node_modules/**', + ] +}; + +export function getLsConfiguration(overrides: Partial): LsConfiguration { + return { + ...defaultConfig, + ...overrides, + }; +} diff --git a/extensions/markdown-language-features/server/src/configuration.ts b/extensions/markdown-language-features/server/src/configuration.ts new file mode 100644 index 00000000000..5066b3110a6 --- /dev/null +++ b/extensions/markdown-language-features/server/src/configuration.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, Emitter } from 'vscode-languageserver'; +import { Disposable } from './util/dispose'; + +export type ValidateEnabled = 'ignore' | 'warning' | 'error'; + +interface Settings { + readonly markdown: { + readonly suggest: { + readonly paths: { + readonly enabled: boolean; + }; + }; + + readonly experimental: { + readonly validate: { + readonly enabled: true; + readonly referenceLinks: { + readonly enabled: ValidateEnabled; + }; + readonly fragmentLinks: { + readonly enabled: ValidateEnabled; + }; + readonly fileLinks: { + readonly enabled: ValidateEnabled; + readonly markdownFragmentLinks: ValidateEnabled; + }; + readonly ignoreLinks: readonly string[]; + }; + }; + }; +} + + +export class ConfigurationManager extends Disposable { + + private readonly _onDidChangeConfiguration = this._register(new Emitter()); + public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; + + private _settings?: Settings; + + constructor(connection: Connection) { + super(); + + // The settings have changed. Is send on server activation as well. + this._register(connection.onDidChangeConfiguration((change) => { + this._settings = change.settings; + this._onDidChangeConfiguration.fire(this._settings!); + })); + } + + public getSettings(): Settings | undefined { + return this._settings; + } +} \ No newline at end of file diff --git a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts new file mode 100644 index 00000000000..9640f052ceb --- /dev/null +++ b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver'; +import * as md from 'vscode-markdown-languageservice'; +import { disposeAll } from 'vscode-markdown-languageservice/out/util/dispose'; +import { Disposable } from 'vscode-notebook-renderer/events'; +import { URI } from 'vscode-uri'; +import { ConfigurationManager, ValidateEnabled } from '../configuration'; +import { VsCodeClientWorkspace } from '../workspace'; + +const defaultDiagnosticOptions: md.DiagnosticOptions = { + validateFileLinks: md.DiagnosticLevel.ignore, + validateReferences: md.DiagnosticLevel.ignore, + validateFragmentLinks: md.DiagnosticLevel.ignore, + validateMarkdownFileLinkFragments: md.DiagnosticLevel.ignore, + ignoreLinks: [], +}; + +function convertDiagnosticLevel(enabled: ValidateEnabled): md.DiagnosticLevel | undefined { + switch (enabled) { + case 'error': return md.DiagnosticLevel.error; + case 'warning': return md.DiagnosticLevel.warning; + case 'ignore': return md.DiagnosticLevel.ignore; + default: return md.DiagnosticLevel.ignore; + } +} + +function getDiagnosticsOptions(config: ConfigurationManager): md.DiagnosticOptions { + const settings = config.getSettings(); + if (!settings) { + return defaultDiagnosticOptions; + } + + return { + validateFileLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.enabled), + validateReferences: convertDiagnosticLevel(settings.markdown.experimental.validate.referenceLinks.enabled), + validateFragmentLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fragmentLinks.enabled), + validateMarkdownFileLinkFragments: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.markdownFragmentLinks), + ignoreLinks: settings.markdown.experimental.validate.ignoreLinks, + }; +} + +export function registerValidateSupport( + connection: Connection, + workspace: VsCodeClientWorkspace, + ls: md.IMdLanguageService, + config: ConfigurationManager, + logger: md.ILogger, +): Disposable { + let diagnosticOptions: md.DiagnosticOptions = defaultDiagnosticOptions; + function updateDiagnosticsSetting(): void { + diagnosticOptions = getDiagnosticsOptions(config); + } + + const subs: Disposable[] = []; + const manager = ls.createPullDiagnosticsManager(); + subs.push(manager); + + subs.push(manager.onLinkedToFileChanged(() => { + // TODO: We only need to refresh certain files + connection.languages.diagnostics.refresh(); + })); + + const emptyDiagnosticsResponse = Object.freeze({ kind: 'full', items: [] }); + + connection.languages.diagnostics.on(async (params, token): Promise => { + logger.log(md.LogLevel.Trace, 'Server: connection.languages.diagnostics.on', params.textDocument.uri); + + if (!config.getSettings()?.markdown.experimental.validate.enabled) { + return emptyDiagnosticsResponse; + } + + const uri = URI.parse(params.textDocument.uri); + if (!workspace.hasMarkdownDocument(uri)) { + return emptyDiagnosticsResponse; + } + + const document = await workspace.openMarkdownDocument(uri); + if (!document) { + return emptyDiagnosticsResponse; + } + + const diagnostics = await manager.computeDiagnostics(document, diagnosticOptions, token); + return { + kind: 'full', + items: diagnostics, + }; + }); + + updateDiagnosticsSetting(); + subs.push(config.onDidChangeConfiguration(() => { + updateDiagnosticsSetting(); + connection.languages.diagnostics.refresh(); + })); + + return { + dispose: () => { + disposeAll(subs); + } + }; +} diff --git a/extensions/markdown-language-features/server/src/logging.ts b/extensions/markdown-language-features/server/src/logging.ts new file mode 100644 index 00000000000..2fc08c25b7a --- /dev/null +++ b/extensions/markdown-language-features/server/src/logging.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import { ILogger, LogLevel } from 'vscode-markdown-languageservice'; + +export class LogFunctionLogger implements ILogger { + + private static now(): string { + const now = new Date(); + return String(now.getUTCHours()).padStart(2, '0') + + ':' + String(now.getMinutes()).padStart(2, '0') + + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0'); + } + + private static data2String(data: any): string { + if (data instanceof Error) { + if (typeof data.stack === 'string') { + return data.stack; + } + return data.message; + } + if (typeof data === 'string') { + return data; + } + return JSON.stringify(data, undefined, 2); + } + + constructor( + private readonly _logFn: typeof console.log + ) { } + + + public log(level: LogLevel, title: string, message: string, data?: any): void { + this.appendLine(`[${level} ${LogFunctionLogger.now()}] ${title}: ${message}`); + if (data) { + this.appendLine(LogFunctionLogger.data2String(data)); + } + } + + private appendLine(value: string): void { + this._logFn(value); + } +} + +export const consoleLogger = new LogFunctionLogger(console.log); diff --git a/extensions/markdown-language-features/server/src/node/main.ts b/extensions/markdown-language-features/server/src/node/main.ts new file mode 100644 index 00000000000..609948b803d --- /dev/null +++ b/extensions/markdown-language-features/server/src/node/main.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, createConnection } from 'vscode-languageserver/node'; +import { startServer } from '../server'; + +// Create a connection for the server. +const connection: Connection = createConnection(); + +console.log = connection.console.log.bind(connection.console); +console.error = connection.console.error.bind(connection.console); + +process.on('unhandledRejection', (e: any) => { + connection.console.error(`Unhandled exception ${e}`); +}); + +startServer(connection); diff --git a/extensions/markdown-language-features/server/src/protocol.ts b/extensions/markdown-language-features/server/src/protocol.ts new file mode 100644 index 00000000000..efb723d5f67 --- /dev/null +++ b/extensions/markdown-language-features/server/src/protocol.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RequestType } from 'vscode-languageserver'; +import type * as lsp from 'vscode-languageserver-types'; +import type * as md from 'vscode-markdown-languageservice'; + +//#region From server +export const parse = new RequestType<{ uri: string }, md.Token[], any>('markdown/parse'); + +export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); +export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); +export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat'); + +export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions; watchParentDirs: boolean }, void, any>('markdown/fs/watcher/create'); +export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete'); + +export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace'); +//#endregion + +//#region To server +export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); +export const getEditForFileRenames = new RequestType, lsp.WorkspaceEdit, any>('markdown/getEditForFileRenames'); + +export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange'); +//#endregion diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts new file mode 100644 index 00000000000..1b46e3712af --- /dev/null +++ b/extensions/markdown-language-features/server/src/server.ts @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, Connection, InitializeParams, InitializeResult, NotebookDocuments, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import * as lsp from 'vscode-languageserver-types'; +import * as md from 'vscode-markdown-languageservice'; +import { URI } from 'vscode-uri'; +import { getLsConfiguration, LsConfiguration } from './config'; +import { ConfigurationManager } from './configuration'; +import { registerValidateSupport } from './languageFeatures/diagnostics'; +import { LogFunctionLogger } from './logging'; +import * as protocol from './protocol'; +import { IDisposable } from './util/dispose'; +import { VsCodeClientWorkspace } from './workspace'; + +interface MdServerInitializationOptions extends LsConfiguration { } + +export async function startServer(connection: Connection) { + const documents = new TextDocuments(TextDocument); + const notebooks = new NotebookDocuments(documents); + + const configurationManager = new ConfigurationManager(connection); + + let mdLs: md.IMdLanguageService | undefined; + let workspace: VsCodeClientWorkspace | undefined; + + connection.onInitialize((params: InitializeParams): InitializeResult => { + const parser = new class implements md.IMdParser { + slugifier = md.githubSlugifier; + + async tokenize(document: md.ITextDocument): Promise { + return await connection.sendRequest(protocol.parse, { uri: document.uri.toString() }); + } + }; + + const initOptions = params.initializationOptions as MdServerInitializationOptions | undefined; + const config = getLsConfiguration(initOptions ?? {}); + + const logger = new LogFunctionLogger(connection.console.log.bind(connection.console)); + workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger); + mdLs = md.createLanguageService({ + workspace, + parser, + logger, + markdownFileExtensions: config.markdownFileExtensions, + excludePaths: config.excludePaths, + }); + + registerCompletionsSupport(connection, documents, mdLs, configurationManager); + registerValidateSupport(connection, workspace, mdLs, configurationManager, logger); + + workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri)); + return { + capabilities: { + diagnosticProvider: { + documentSelector: null, + identifier: 'markdown', + interFileDependencies: true, + workspaceDiagnostics: false, + }, + completionProvider: { triggerCharacters: ['.', '/', '#'] }, + definitionProvider: true, + documentLinkProvider: { resolveProvider: true }, + documentSymbolProvider: true, + foldingRangeProvider: true, + renameProvider: { prepareProvider: true, }, + selectionRangeProvider: true, + workspaceSymbolProvider: true, + workspace: { + workspaceFolders: { + supported: true, + changeNotifications: true, + }, + } + } + }; + }); + + connection.onDocumentLinks(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; + } + return mdLs!.getDocumentLinks(document, token); + }); + + connection.onDocumentLinkResolve(async (link, token): Promise => { + return mdLs!.resolveDocumentLink(link, token); + }); + + connection.onDocumentSymbol(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; + } + return mdLs!.getDocumentSymbols(document, token); + }); + + connection.onFoldingRanges(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; + } + return mdLs!.getFoldingRanges(document, token); + }); + + connection.onSelectionRanges(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; + } + return mdLs!.getSelectionRanges(document, params.positions, token); + }); + + connection.onWorkspaceSymbol(async (params, token): Promise => { + return mdLs!.getWorkspaceSymbols(params.query, token); + }); + + connection.onReferences(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return []; + } + return mdLs!.getReferences(document, params.position, params.context, token); + }); + + connection.onDefinition(async (params, token): Promise => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; + } + return mdLs!.getDefinition(document, params.position, token); + }); + + connection.onPrepareRename(async (params, token) => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; + } + return mdLs!.prepareRename(document, params.position, token); + }); + + connection.onRenameRequest(async (params, token) => { + const document = documents.get(params.textDocument.uri); + if (!document) { + return undefined; + } + return mdLs!.getRenameEdit(document, params.position, params.newName, token); + }); + + connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => { + return mdLs!.getFileReferences(URI.parse(params.uri), token); + })); + + connection.onRequest(protocol.getEditForFileRenames, (async (params, token: CancellationToken) => { + return mdLs!.getRenameFilesInWorkspaceEdit(params.map(x => ({ oldUri: URI.parse(x.oldUri), newUri: URI.parse(x.newUri) })), token); + })); + + documents.listen(connection); + notebooks.listen(connection); + connection.listen(); +} + + +function registerCompletionsSupport( + connection: Connection, + documents: TextDocuments, + ls: md.IMdLanguageService, + config: ConfigurationManager, +): IDisposable { + // let registration: Promise | undefined; + function update() { + // TODO: client still makes the request in this case. Figure our how to properly unregister. + return; + // const settings = config.getSettings(); + // if (settings?.markdown.suggest.paths.enabled) { + // if (!registration) { + // registration = connection.client.register(CompletionRequest.type); + // } + // } else { + // registration?.then(x => x.dispose()); + // registration = undefined; + // } + } + + connection.onCompletion(async (params, token): Promise => { + try { + const settings = config.getSettings(); + if (!settings?.markdown.suggest.paths.enabled) { + return []; + } + + const document = documents.get(params.textDocument.uri); + if (document) { + return await ls.getCompletionItems(document, params.position, params.context!, token); + } + } catch (e) { + console.error(e.stack); + } + return []; + }); + + update(); + return config.onDidChangeConfiguration(() => update()); +} diff --git a/extensions/markdown-language-features/server/src/util/arrays.ts b/extensions/markdown-language-features/server/src/util/arrays.ts new file mode 100644 index 00000000000..3ed55d8f077 --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/arrays.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * @returns New array with all falsy values removed. The original array IS NOT modified. + */ +export function coalesce(array: ReadonlyArray): T[] { + return array.filter(e => !!e); +} diff --git a/extensions/markdown-language-features/server/src/util/dispose.ts b/extensions/markdown-language-features/server/src/util/dispose.ts new file mode 100644 index 00000000000..eee79003ae9 --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/dispose.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class MultiDisposeError extends Error { + constructor( + public readonly errors: any[] + ) { + super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); + } +} + +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + + for (const disposable of disposables) { + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new MultiDisposeError(errors); + } +} + +export interface IDisposable { + dispose(): void; +} + +export abstract class Disposable { + private _isDisposed = false; + + protected _disposables: IDisposable[] = []; + + public dispose(): any { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + disposeAll(this._disposables); + } + + protected _register(value: T): T { + if (this._isDisposed) { + value.dispose(); + } else { + this._disposables.push(value); + } + return value; + } + + protected get isDisposed() { + return this._isDisposed; + } +} + +export class DisposableStore extends Disposable { + private readonly items = new Set(); + + public override dispose() { + super.dispose(); + disposeAll(this.items); + this.items.clear(); + } + + public add(item: T): T { + if (this.isDisposed) { + console.warn('Adding to disposed store. Item will be leaked'); + } + + this.items.add(item); + return item; + } +} diff --git a/extensions/markdown-language-features/server/src/util/file.ts b/extensions/markdown-language-features/server/src/util/file.ts new file mode 100644 index 00000000000..b8d1286a42c --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/file.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument } from 'vscode-languageserver-textdocument'; +import { URI, Utils } from 'vscode-uri'; +import { LsConfiguration } from '../config'; + +export function looksLikeMarkdownPath(config: LsConfiguration, resolvedHrefPath: URI) { + return config.markdownFileExtensions.includes(Utils.extname(URI.from(resolvedHrefPath)).toLowerCase().replace('.', '')); +} + +export function isMarkdownFile(document: TextDocument) { + return document.languageId === 'markdown'; +} diff --git a/extensions/markdown-language-features/server/src/util/limiter.ts b/extensions/markdown-language-features/server/src/util/limiter.ts new file mode 100644 index 00000000000..bd4153cd08b --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/limiter.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface ILimitedTaskFactory { + factory: ITask>; + c: (value: T | Promise) => void; + e: (error?: unknown) => void; +} + +interface ITask { + (): T; +} + +/** + * A helper to queue N promises and run them all with a max degree of parallelism. The helper + * ensures that at any time no more than M promises are running at the same time. + * + * Taken from 'src/vs/base/common/async.ts' + */ +export class Limiter { + + private _size = 0; + private runningPromises: number; + private readonly maxDegreeOfParalellism: number; + private readonly outstandingPromises: ILimitedTaskFactory[]; + + constructor(maxDegreeOfParalellism: number) { + this.maxDegreeOfParalellism = maxDegreeOfParalellism; + this.outstandingPromises = []; + this.runningPromises = 0; + } + + get size(): number { + return this._size; + } + + queue(factory: ITask>): Promise { + this._size++; + + return new Promise((c, e) => { + this.outstandingPromises.push({ factory, c, e }); + this.consume(); + }); + } + + private consume(): void { + while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { + const iLimitedTask = this.outstandingPromises.shift()!; + this.runningPromises++; + + const promise = iLimitedTask.factory(); + promise.then(iLimitedTask.c, iLimitedTask.e); + promise.then(() => this.consumed(), () => this.consumed()); + } + } + + private consumed(): void { + this._size--; + this.runningPromises--; + + if (this.outstandingPromises.length > 0) { + this.consume(); + } + } +} diff --git a/extensions/markdown-language-features/server/src/util/resourceMap.ts b/extensions/markdown-language-features/server/src/util/resourceMap.ts new file mode 100644 index 00000000000..7cec9d661d3 --- /dev/null +++ b/extensions/markdown-language-features/server/src/util/resourceMap.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vscode-uri'; + + +type ResourceToKey = (uri: URI) => string; + +const defaultResourceToKey = (resource: URI): string => resource.toString(); + +export class ResourceMap { + + private readonly map = new Map(); + + private readonly toKey: ResourceToKey; + + constructor(toKey: ResourceToKey = defaultResourceToKey) { + this.toKey = toKey; + } + + public set(uri: URI, value: T): this { + this.map.set(this.toKey(uri), { uri, value }); + return this; + } + + public get(resource: URI): T | undefined { + return this.map.get(this.toKey(resource))?.value; + } + + public has(resource: URI): boolean { + return this.map.has(this.toKey(resource)); + } + + public get size(): number { + return this.map.size; + } + + public clear(): void { + this.map.clear(); + } + + public delete(resource: URI): boolean { + return this.map.delete(this.toKey(resource)); + } + + public *values(): IterableIterator { + for (const entry of this.map.values()) { + yield entry.value; + } + } + + public *keys(): IterableIterator { + for (const entry of this.map.values()) { + yield entry.uri; + } + } + + public *entries(): IterableIterator<[URI, T]> { + for (const entry of this.map.values()) { + yield [entry.uri, entry.value]; + } + } + + public [Symbol.iterator](): IterableIterator<[URI, T]> { + return this.entries(); + } +} diff --git a/src/vs/css.d.ts b/extensions/markdown-language-features/server/src/util/schemes.ts similarity index 81% rename from src/vs/css.d.ts rename to extensions/markdown-language-features/server/src/util/schemes.ts index 11a10ed5950..67b75e0a0d6 100644 --- a/src/vs/css.d.ts +++ b/extensions/markdown-language-features/server/src/util/schemes.ts @@ -3,3 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +export const Schemes = Object.freeze({ + notebookCell: 'vscode-notebook-cell', +}); diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts new file mode 100644 index 00000000000..3cd75cf5e84 --- /dev/null +++ b/extensions/markdown-language-features/server/src/workspace.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; +import * as md from 'vscode-markdown-languageservice'; +import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace'; +import { URI } from 'vscode-uri'; +import { LsConfiguration } from './config'; +import * as protocol from './protocol'; +import { coalesce } from './util/arrays'; +import { isMarkdownFile, looksLikeMarkdownPath } from './util/file'; +import { Limiter } from './util/limiter'; +import { ResourceMap } from './util/resourceMap'; +import { Schemes } from './util/schemes'; + +declare const TextDecoder: any; + +export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching { + + private readonly _onDidCreateMarkdownDocument = new Emitter(); + public readonly onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocument.event; + + private readonly _onDidChangeMarkdownDocument = new Emitter(); + public readonly onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocument.event; + + private readonly _onDidDeleteMarkdownDocument = new Emitter(); + public readonly onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocument.event; + + private readonly _documentCache = new ResourceMap(); + + private readonly _utf8Decoder = new TextDecoder('utf-8'); + + private _watcherPool = 0; + private readonly _watchers = new Map; + readonly onDidCreate: Emitter; + readonly onDidDelete: Emitter; + }>(); + + constructor( + private readonly connection: Connection, + private readonly config: LsConfiguration, + private readonly documents: TextDocuments, + private readonly notebooks: NotebookDocuments, + private readonly logger: md.ILogger, + ) { + documents.onDidOpen(e => { + this._documentCache.delete(URI.parse(e.document.uri)); + if (this.isRelevantMarkdownDocument(e.document)) { + this._onDidCreateMarkdownDocument.fire(e.document); + } + }); + + documents.onDidChangeContent(e => { + if (this.isRelevantMarkdownDocument(e.document)) { + this._onDidChangeMarkdownDocument.fire(e.document); + } + }); + + documents.onDidClose(e => { + const uri = URI.parse(e.document.uri); + this._documentCache.delete(uri); + + if (this.isRelevantMarkdownDocument(e.document)) { + this._onDidDeleteMarkdownDocument.fire(uri); + } + }); + + connection.onDidChangeWatchedFiles(async ({ changes }) => { + for (const change of changes) { + const resource = URI.parse(change.uri); + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: onDidChangeWatchedFiles', `${change.type}: ${resource}`); + switch (change.type) { + case FileChangeType.Changed: { + this._documentCache.delete(resource); + const document = await this.openMarkdownDocument(resource); + if (document) { + this._onDidChangeMarkdownDocument.fire(document); + } + break; + } + case FileChangeType.Created: { + const document = await this.openMarkdownDocument(resource); + if (document) { + this._onDidCreateMarkdownDocument.fire(document); + } + break; + } + case FileChangeType.Deleted: { + this._documentCache.delete(resource); + this._onDidDeleteMarkdownDocument.fire(resource); + break; + } + } + } + }); + + connection.onRequest(protocol.fs_watcher_onChange, params => { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: fs_watcher_onChange', `${params.kind}: ${params.uri}`); + + const watcher = this._watchers.get(params.id); + if (!watcher) { + return; + } + + switch (params.kind) { + case 'create': watcher.onDidCreate.fire(URI.parse(params.uri)); return; + case 'change': watcher.onDidChange.fire(URI.parse(params.uri)); return; + case 'delete': watcher.onDidDelete.fire(URI.parse(params.uri)); return; + } + }); + } + + public listen() { + this.connection.workspace.onDidChangeWorkspaceFolders(async () => { + this.workspaceFolders = (await this.connection.workspace.getWorkspaceFolders() ?? []).map(x => URI.parse(x.uri)); + }); + } + + private _workspaceFolders: readonly URI[] = []; + + get workspaceFolders(): readonly URI[] { + return this._workspaceFolders; + } + + set workspaceFolders(value: readonly URI[]) { + this._workspaceFolders = value; + } + + async getAllMarkdownDocuments(): Promise> { + const maxConcurrent = 20; + + const foundFiles = new ResourceMap(); + const limiter = new Limiter(maxConcurrent); + + // Add files on disk + const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {}); + const onDiskResults = await Promise.all(resources.map(strResource => { + return limiter.queue(async () => { + const resource = URI.parse(strResource); + const doc = await this.openMarkdownDocument(resource); + if (doc) { + foundFiles.set(resource); + } + return doc; + }); + })); + + // Add opened files (such as untitled files) + const openTextDocumentResults = await Promise.all(this.documents.all() + .filter(doc => !foundFiles.has(URI.parse(doc.uri)) && this.isRelevantMarkdownDocument(doc))); + + return coalesce([...onDiskResults, ...openTextDocumentResults]); + } + + hasMarkdownDocument(resource: URI): boolean { + return !!this.documents.get(resource.toString()); + } + + async openMarkdownDocument(resource: URI): Promise { + const existing = this._documentCache.get(resource); + if (existing) { + return existing; + } + + const matchingDocument = this.documents.get(resource.toString()); + if (matchingDocument) { + this._documentCache.set(resource, matchingDocument); + return matchingDocument; + } + + if (!looksLikeMarkdownPath(this.config, resource)) { + return undefined; + } + + try { + const response = await this.connection.sendRequest(protocol.fs_readFile, { uri: resource.toString() }); + // TODO: LSP doesn't seem to handle Array buffers well + const bytes = new Uint8Array(response); + + // We assume that markdown is in UTF-8 + const text = this._utf8Decoder.decode(bytes); + const doc = TextDocument.create(resource.toString(), 'markdown', 0, text); + this._documentCache.set(resource, doc); + return doc; + } catch (e) { + return undefined; + } + } + + async stat(resource: URI): Promise { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: stat', `${resource}`); + if (this._documentCache.has(resource) || this.documents.get(resource.toString())) { + return { isDirectory: false }; + } + return this.connection.sendRequest(protocol.fs_stat, { uri: resource.toString() }); + } + + async readDirectory(resource: URI): Promise<[string, md.FileStat][]> { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`); + return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() }); + } + + getContainingDocument(resource: URI): ContainingDocumentContext | undefined { + if (resource.scheme === Schemes.notebookCell) { + const nb = this.notebooks.findNotebookDocumentForCell(resource.toString()); + if (nb) { + return { + uri: URI.parse(nb.uri), + children: nb.cells.map(cell => ({ uri: URI.parse(cell.document) })), + }; + } + } + return undefined; + } + + watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher { + const id = this._watcherPool++; + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: watchFile', `(${id}) ${resource}`); + + const entry = { + resource, + options, + onDidCreate: new Emitter(), + onDidChange: new Emitter(), + onDidDelete: new Emitter(), + }; + this._watchers.set(id, entry); + + this.connection.sendRequest(protocol.fs_watcher_create, { + id, + uri: resource.toString(), + options, + watchParentDirs: true, + }); + + return { + onDidCreate: entry.onDidCreate.event, + onDidChange: entry.onDidChange.event, + onDidDelete: entry.onDidDelete.event, + dispose: () => { + this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: disposeWatcher', `(${id}) ${resource}`); + this.connection.sendRequest(protocol.fs_watcher_delete, { id }); + this._watchers.delete(id); + } + }; + } + + private isRelevantMarkdownDocument(doc: TextDocument) { + return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview'; + } +} diff --git a/extensions/markdown-language-features/server/tsconfig.json b/extensions/markdown-language-features/server/tsconfig.json new file mode 100644 index 00000000000..8b4aedde27d --- /dev/null +++ b/extensions/markdown-language-features/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "src/**/*" + ] +} diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock new file mode 100644 index 00000000000..059784fec55 --- /dev/null +++ b/extensions/markdown-language-features/server/yarn.lock @@ -0,0 +1,64 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@16.x": + version "16.11.47" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.47.tgz#efa9e3e0f72e7aa6a138055dace7437a83d9f91c" + integrity sha512-fpP+jk2zJ4VW66+wAMFoBJlx1bxmBKx4DUFf68UHgdGCOuyUTDlLWqsaNPJh7xhNDykyJ9eIzAygilP/4WoN8g== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +vscode-jsonrpc@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz#f239ed2cd6004021b6550af9fd9d3e47eee3cac9" + integrity sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ== + +vscode-languageserver-protocol@3.17.2: + version "3.17.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz#beaa46aea06ed061576586c5e11368a9afc1d378" + integrity sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg== + dependencies: + vscode-jsonrpc "8.0.2" + vscode-languageserver-types "3.17.2" + +vscode-languageserver-textdocument@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + +vscode-languageserver-types@3.17.2, vscode-languageserver-types@^3.17.1: + version "3.17.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2" + integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA== + +vscode-languageserver@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2.tgz#cfe2f0996d9dfd40d3854e786b2821604dfec06d" + integrity sha512-bpEt2ggPxKzsAOZlXmCJ50bV7VrxwCS5BI4+egUmure/oI/t4OlFzi/YNtVvY24A2UDOZAgwFGgnZPwqSJubkA== + dependencies: + vscode-languageserver-protocol "3.17.2" + +vscode-markdown-languageservice@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0.tgz#029822a0c95fd4040ed3a2154b640ddb87160743" + integrity sha512-Qux6lErBmasjBnDtK6Ff7LZxdrFl29ChxaJWLuT+p67UcAZt3UdCcFjll+BpZJ8hLKfsVGEG6rdYFAuRXaty9Q== + dependencies: + picomatch "^2.3.1" + vscode-languageserver-textdocument "^1.0.5" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" + vscode-uri "^3.0.3" + +vscode-nls@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== + +vscode-uri@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" + integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== diff --git a/extensions/markdown-language-features/src/client.ts b/extensions/markdown-language-features/src/client.ts new file mode 100644 index 00000000000..0695e275e25 --- /dev/null +++ b/extensions/markdown-language-features/src/client.ts @@ -0,0 +1,211 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType } from 'vscode-languageclient'; +import { disposeAll, IDisposable } from 'vscode-markdown-languageservice/out/util/dispose'; +import { ResourceMap } from 'vscode-markdown-languageservice/out/util/resourceMap'; +import * as nls from 'vscode-nls'; +import { Utils } from 'vscode-uri'; +import { IMdParser } from './markdownEngine'; +import * as proto from './protocol'; +import { looksLikeMarkdownPath, markdownFileExtensions } from './util/file'; +import { Schemes } from './util/schemes'; +import { IMdWorkspace } from './workspace'; + +const localize = nls.loadMessageBundle(); + +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; + + +export async function startClient(factory: LanguageClientConstructor, workspace: IMdWorkspace, parser: IMdParser): Promise { + + const mdFileGlob = `**/*.{${markdownFileExtensions.join(',')}}`; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ language: 'markdown' }], + synchronize: { + configurationSection: ['markdown'], + fileEvents: vscode.workspace.createFileSystemWatcher(mdFileGlob), + }, + initializationOptions: { + markdownFileExtensions, + }, + diagnosticPullOptions: { + onChange: true, + onSave: true, + onTabs: true, + match(_documentSelector, resource) { + return looksLikeMarkdownPath(resource); + }, + }, + + }; + + const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions); + + client.registerProposedFeatures(); + + const notebookFeature = client.getFeature(NotebookDocumentSyncRegistrationType.method); + if (notebookFeature !== undefined) { + notebookFeature.register({ + id: String(Date.now()), + registerOptions: { + notebookSelector: [{ + notebook: '*', + cells: [{ language: 'markdown' }] + }] + } + }); + } + + client.onRequest(proto.parse, async (e) => { + const uri = vscode.Uri.parse(e.uri); + const doc = await workspace.getOrLoadMarkdownDocument(uri); + if (doc) { + return parser.tokenize(doc); + } else { + return []; + } + }); + + client.onRequest(proto.fs_readFile, async (e): Promise => { + const uri = vscode.Uri.parse(e.uri); + return Array.from(await vscode.workspace.fs.readFile(uri)); + }); + + client.onRequest(proto.fs_stat, async (e): Promise<{ isDirectory: boolean } | undefined> => { + const uri = vscode.Uri.parse(e.uri); + try { + const stat = await vscode.workspace.fs.stat(uri); + return { isDirectory: stat.type === vscode.FileType.Directory }; + } catch { + return undefined; + } + }); + + client.onRequest(proto.fs_readDirectory, async (e): Promise<[string, { isDirectory: boolean }][]> => { + const uri = vscode.Uri.parse(e.uri); + const result = await vscode.workspace.fs.readDirectory(uri); + return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]); + }); + + client.onRequest(proto.findMarkdownFilesInWorkspace, async (): Promise => { + return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString()); + }); + + const watchers = new FileWatcherManager(); + + client.onRequest(proto.fs_watcher_create, async (params): Promise => { + const id = params.id; + const uri = vscode.Uri.parse(params.uri); + + const sendWatcherChange = (kind: 'create' | 'change' | 'delete') => { + client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind }); + }; + + watchers.create(id, uri, params.watchParentDirs, { + create: params.options.ignoreCreate ? undefined : () => sendWatcherChange('create'), + change: params.options.ignoreChange ? undefined : () => sendWatcherChange('change'), + delete: params.options.ignoreDelete ? undefined : () => sendWatcherChange('delete'), + }); + }); + + client.onRequest(proto.fs_watcher_delete, async (params): Promise => { + watchers.delete(params.id); + }); + + await client.start(); + + return client; +} + +type DirWatcherEntry = { + readonly uri: vscode.Uri; + readonly listeners: IDisposable[]; +}; + +class FileWatcherManager { + + private readonly fileWatchers = new Map(); + + private readonly dirWatchers = new ResourceMap<{ + readonly watcher: vscode.FileSystemWatcher; + refCount: number; + }>(); + + create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void { + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete); + const parentDirWatchers: DirWatcherEntry[] = []; + this.fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers }); + + if (listeners.create) { watcher.onDidCreate(listeners.create); } + if (listeners.change) { watcher.onDidChange(listeners.change); } + if (listeners.delete) { watcher.onDidDelete(listeners.delete); } + + if (watchParentDirs && uri.scheme !== Schemes.untitled) { + // We need to watch the parent directories too for when these are deleted / created + for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { + const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; + + let parentDirWatcher = this.dirWatchers.get(dirUri); + if (!parentDirWatcher) { + const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri)); + const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete); + parentDirWatcher = { refCount: 0, watcher: parentWatcher }; + this.dirWatchers.set(dirUri, parentDirWatcher); + } + parentDirWatcher.refCount++; + + if (listeners.create) { + dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { + // Just because the parent dir was created doesn't mean our file was created + try { + const stat = await vscode.workspace.fs.stat(uri); + if (stat.type === vscode.FileType.File) { + listeners.create!(); + } + } catch { + // Noop + } + })); + } + + if (listeners.delete) { + // When the parent dir is deleted, consider our file deleted too + + // TODO: this fires if the file previously did not exist and then the parent is deleted + dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); + } + + parentDirWatchers.push(dirWatcher); + } + } + } + + delete(id: number): void { + const entry = this.fileWatchers.get(id); + if (entry) { + for (const dirWatcher of entry.dirWatchers) { + disposeAll(dirWatcher.listeners); + + const dirWatcherEntry = this.dirWatchers.get(dirWatcher.uri); + if (dirWatcherEntry) { + if (--dirWatcherEntry.refCount <= 0) { + dirWatcherEntry.watcher.dispose(); + this.dirWatchers.delete(dirWatcher.uri); + } + } + } + + entry.watcher.dispose(); + } + + this.fileWatchers.delete(id); + } +} diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts index 060e280c7f6..1b061c9c4e4 100644 --- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/commands/openDocumentLink.ts @@ -5,8 +5,9 @@ import * as vscode from 'vscode'; import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { openDocumentLink } from '../util/openDocumentLink'; +import { Schemes } from '../util/schemes'; type UriComponents = { readonly scheme?: string; @@ -48,18 +49,18 @@ export class OpenDocumentLinkCommand implements Command { } public constructor( - private readonly engine: MarkdownEngine + private readonly tocProvider: MdTableOfContentsProvider, ) { } public async execute(args: OpenDocumentLinkArgs) { const fromResource = vscode.Uri.parse('').with(args.fromResource); const targetResource = reviveUri(args.parts).with({ fragment: args.fragment }); - return openDocumentLink(this.engine, targetResource, fromResource); + return openDocumentLink(this.tocProvider, targetResource, fromResource); } } function reviveUri(parts: any) { - if (parts.scheme === 'file') { + if (parts.scheme === Schemes.file) { return vscode.Uri.file(parts.path); } return vscode.Uri.parse('').with(parts); diff --git a/extensions/markdown-language-features/src/commands/refreshPreview.ts b/extensions/markdown-language-features/src/commands/refreshPreview.ts index ec8888b5c59..c75d79347ac 100644 --- a/extensions/markdown-language-features/src/commands/refreshPreview.ts +++ b/extensions/markdown-language-features/src/commands/refreshPreview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownPreviewManager } from '../preview/previewManager'; export class RefreshPreviewCommand implements Command { @@ -12,7 +12,7 @@ export class RefreshPreviewCommand implements Command { public constructor( private readonly webviewManager: MarkdownPreviewManager, - private readonly engine: MarkdownEngine + private readonly engine: MarkdownItEngine ) { } public execute() { diff --git a/extensions/markdown-language-features/src/commands/reloadPlugins.ts b/extensions/markdown-language-features/src/commands/reloadPlugins.ts index ffa69b6977c..640ea9832b9 100644 --- a/extensions/markdown-language-features/src/commands/reloadPlugins.ts +++ b/extensions/markdown-language-features/src/commands/reloadPlugins.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownPreviewManager } from '../preview/previewManager'; export class ReloadPlugins implements Command { @@ -12,7 +12,7 @@ export class ReloadPlugins implements Command { public constructor( private readonly webviewManager: MarkdownPreviewManager, - private readonly engine: MarkdownEngine, + private readonly engine: MarkdownItEngine, ) { } public execute(): void { diff --git a/extensions/markdown-language-features/src/commands/renderDocument.ts b/extensions/markdown-language-features/src/commands/renderDocument.ts index 098f2da771a..675f4ce196c 100644 --- a/extensions/markdown-language-features/src/commands/renderDocument.ts +++ b/extensions/markdown-language-features/src/commands/renderDocument.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from '../commandManager'; -import { MarkdownEngine } from '../markdownEngine'; -import { SkinnyTextDocument } from '../workspaceContents'; +import { MarkdownItEngine } from '../markdownEngine'; +import { ITextDocument } from '../types/textDocument'; export class RenderDocument implements Command { public readonly id = 'markdown.api.render'; public constructor( - private readonly engine: MarkdownEngine + private readonly engine: MarkdownItEngine ) { } - public async execute(document: SkinnyTextDocument | string): Promise { + public async execute(document: ITextDocument | string): Promise { return (await (this.engine.render(document))).html; } } diff --git a/extensions/markdown-language-features/src/extension.browser.ts b/extensions/markdown-language-features/src/extension.browser.ts new file mode 100644 index 00000000000..456a3811e45 --- /dev/null +++ b/extensions/markdown-language-features/src/extension.browser.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { BaseLanguageClient, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser'; +import { startClient } from './client'; +import { activateShared } from './extension.shared'; +import { VsCodeOutputLogger } from './logging'; +import { IMdParser, MarkdownItEngine } from './markdownEngine'; +import { getMarkdownExtensionContributions } from './markdownExtensions'; +import { githubSlugifier } from './slugify'; +import { IMdWorkspace, VsCodeMdWorkspace } from './workspace'; + +export async function activate(context: vscode.ExtensionContext) { + const contributions = getMarkdownExtensionContributions(context); + context.subscriptions.push(contributions); + + const logger = new VsCodeOutputLogger(); + context.subscriptions.push(logger); + + const engine = new MarkdownItEngine(contributions, githubSlugifier, logger); + + const workspace = new VsCodeMdWorkspace(); + context.subscriptions.push(workspace); + + const client = await startServer(context, workspace, engine); + context.subscriptions.push({ + dispose: () => client.stop() + }); + activateShared(context, client, workspace, engine, logger, contributions); +} + +function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise { + const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/main.js'); + const worker = new Worker(serverMain.toString()); + + return startClient((id: string, name: string, clientOptions: LanguageClientOptions) => { + return new LanguageClient(id, name, clientOptions, worker); + }, workspace, parser); +} diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts new file mode 100644 index 00000000000..4f5589e14f3 --- /dev/null +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { BaseLanguageClient } from 'vscode-languageclient'; +import { CommandManager } from './commandManager'; +import * as commands from './commands/index'; +import { registerPasteSupport } from './languageFeatures/copyPaste'; +import { registerDiagnosticSupport } from './languageFeatures/diagnostics'; +import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor'; +import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences'; +import { registerUpdateLinksOnRename } from './languageFeatures/updatePathsOnRename'; +import { ILogger } from './logging'; +import { MarkdownItEngine, MdParsingProvider } from './markdownEngine'; +import { MarkdownContributionProvider } from './markdownExtensions'; +import { MdDocumentRenderer } from './preview/documentRenderer'; +import { MarkdownPreviewManager } from './preview/previewManager'; +import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security'; +import { MdTableOfContentsProvider } from './tableOfContents'; +import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; +import { IMdWorkspace } from './workspace'; + +export function activateShared( + context: vscode.ExtensionContext, + client: BaseLanguageClient, + workspace: IMdWorkspace, + engine: MarkdownItEngine, + logger: ILogger, + contributions: MarkdownContributionProvider, +) { + const telemetryReporter = loadDefaultTelemetryReporter(); + context.subscriptions.push(telemetryReporter); + + const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); + const commandManager = new CommandManager(); + + const parser = new MdParsingProvider(engine, workspace); + const tocProvider = new MdTableOfContentsProvider(parser, workspace, logger); + context.subscriptions.push(parser, tocProvider); + + const contentProvider = new MdDocumentRenderer(engine, context, cspArbiter, contributions, logger); + const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider); + context.subscriptions.push(previewManager); + + context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager)); + context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider)); + + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { + previewManager.updateConfiguration(); + })); +} + +function registerMarkdownLanguageFeatures( + client: BaseLanguageClient, + commandManager: CommandManager, +): vscode.Disposable { + const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; + return vscode.Disposable.from( + // Language features + registerDiagnosticSupport(selector, commandManager), + registerDropIntoEditorSupport(selector), + registerFindFileReferenceSupport(commandManager, client), + registerPasteSupport(selector), + registerUpdateLinksOnRename(client), + ); +} + +function registerMarkdownCommands( + commandManager: CommandManager, + previewManager: MarkdownPreviewManager, + telemetryReporter: TelemetryReporter, + cspArbiter: ContentSecurityPolicyArbiter, + engine: MarkdownItEngine, + tocProvider: MdTableOfContentsProvider, +): vscode.Disposable { + const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); + + commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter)); + commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter)); + commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter)); + commandManager.register(new commands.ShowSourceCommand(previewManager)); + commandManager.register(new commands.RefreshPreviewCommand(previewManager, engine)); + commandManager.register(new commands.MoveCursorToPositionCommand()); + commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager)); + commandManager.register(new commands.OpenDocumentLinkCommand(tocProvider)); + commandManager.register(new commands.ToggleLockCommand(previewManager)); + commandManager.register(new commands.RenderDocument(engine)); + commandManager.register(new commands.ReloadPlugins(previewManager, engine)); + return commandManager; +} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 0f2399692e9..9f68ef2c1f2 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -4,105 +4,50 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { CommandManager } from './commandManager'; -import * as commands from './commands/index'; -import { registerPasteProvider } from './languageFeatures/copyPaste'; -import { MdDefinitionProvider } from './languageFeatures/definitionProvider'; -import { register as registerDiagnostics } from './languageFeatures/diagnostics'; -import { MdLinkProvider } from './languageFeatures/documentLinkProvider'; -import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider'; -import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor'; -import { registerFindFileReferences } from './languageFeatures/fileReferences'; -import { MdFoldingProvider } from './languageFeatures/foldingProvider'; -import { MdPathCompletionProvider } from './languageFeatures/pathCompletions'; -import { MdReferencesProvider } from './languageFeatures/references'; -import { MdRenameProvider } from './languageFeatures/rename'; -import { MdSmartSelect } from './languageFeatures/smartSelect'; -import { MdWorkspaceSymbolProvider } from './languageFeatures/workspaceSymbolProvider'; -import { Logger } from './logger'; -import { MarkdownEngine } from './markdownEngine'; +import { BaseLanguageClient, LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node'; +import { startClient } from './client'; +import { activateShared } from './extension.shared'; +import { VsCodeOutputLogger } from './logging'; +import { IMdParser, MarkdownItEngine } from './markdownEngine'; import { getMarkdownExtensionContributions } from './markdownExtensions'; -import { MarkdownContentProvider } from './preview/previewContentProvider'; -import { MarkdownPreviewManager } from './preview/previewManager'; -import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security'; import { githubSlugifier } from './slugify'; -import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; -import { VsCodeMdWorkspaceContents } from './workspaceContents'; - - -export function activate(context: vscode.ExtensionContext) { - const telemetryReporter = loadDefaultTelemetryReporter(); - context.subscriptions.push(telemetryReporter); +import { IMdWorkspace, VsCodeMdWorkspace } from './workspace'; +export async function activate(context: vscode.ExtensionContext) { const contributions = getMarkdownExtensionContributions(context); context.subscriptions.push(contributions); - const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); - const engine = new MarkdownEngine(contributions, githubSlugifier); - const logger = new Logger(); - const commandManager = new CommandManager(); + const logger = new VsCodeOutputLogger(); + context.subscriptions.push(logger); - const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger); - const symbolProvider = new MdDocumentSymbolProvider(engine); - const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine); - context.subscriptions.push(previewManager); + const engine = new MarkdownItEngine(contributions, githubSlugifier, logger); - context.subscriptions.push(registerMarkdownLanguageFeatures(commandManager, symbolProvider, engine)); - context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine)); + const workspace = new VsCodeMdWorkspace(); + context.subscriptions.push(workspace); - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { - logger.updateConfiguration(); - previewManager.updateConfiguration(); - })); + const client = await startServer(context, workspace, engine); + context.subscriptions.push({ + dispose: () => client.stop() + }); + activateShared(context, client, workspace, engine, logger, contributions); } -function registerMarkdownLanguageFeatures( - commandManager: CommandManager, - symbolProvider: MdDocumentSymbolProvider, - engine: MarkdownEngine -): vscode.Disposable { - const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; +function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise { + const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || ''; - const linkProvider = new MdLinkProvider(engine); - const workspaceContents = new VsCodeMdWorkspaceContents(); + const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/main`; + const serverModule = context.asAbsolutePath(serverMain); - const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - return vscode.Disposable.from( - vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider), - vscode.languages.registerDocumentLinkProvider(selector, linkProvider), - vscode.languages.registerFoldingRangeProvider(selector, new MdFoldingProvider(engine)), - vscode.languages.registerSelectionRangeProvider(selector, new MdSmartSelect(engine)), - vscode.languages.registerWorkspaceSymbolProvider(new MdWorkspaceSymbolProvider(symbolProvider, workspaceContents)), - vscode.languages.registerReferenceProvider(selector, referencesProvider), - vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier)), - vscode.languages.registerDefinitionProvider(selector, new MdDefinitionProvider(referencesProvider)), - MdPathCompletionProvider.register(selector, engine, linkProvider), - registerDiagnostics(selector, engine, workspaceContents, linkProvider, commandManager), - registerDropIntoEditor(selector), - registerPasteProvider(selector), - registerFindFileReferences(commandManager, referencesProvider), - ); -} - -function registerMarkdownCommands( - commandManager: CommandManager, - previewManager: MarkdownPreviewManager, - telemetryReporter: TelemetryReporter, - cspArbiter: ContentSecurityPolicyArbiter, - engine: MarkdownEngine -): vscode.Disposable { - const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); - - commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter)); - commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter)); - commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter)); - commandManager.register(new commands.ShowSourceCommand(previewManager)); - commandManager.register(new commands.RefreshPreviewCommand(previewManager, engine)); - commandManager.register(new commands.MoveCursorToPositionCommand()); - commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager)); - commandManager.register(new commands.OpenDocumentLinkCommand(engine)); - commandManager.register(new commands.ToggleLockCommand(previewManager)); - commandManager.register(new commands.RenderDocument(engine)); - commandManager.register(new commands.ReloadPlugins(previewManager, engine)); - return commandManager; + // The debug options for the server + const debugOptions = { execArgv: ['--nolazy', '--inspect=' + (7000 + Math.round(Math.random() * 999))] }; + + // If the extension is launch in debug mode the debug server options are use + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + }; + return startClient((id, name, clientOptions) => { + return new LanguageClient(id, name, serverOptions, clientOptions); + }, workspace, parser); } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts index 242bc4a0f52..9c5dbe5dacd 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { tryGetUriListSnippet } from './dropIntoEditor'; -export function registerPasteProvider(selector: vscode.DocumentSelector) { +export function registerPasteSupport(selector: vscode.DocumentSelector) { return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider { async provideDocumentPasteEdits( @@ -15,7 +15,7 @@ export function registerPasteProvider(selector: vscode.DocumentSelector) { dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken, ): Promise { - const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', false); + const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', true); if (!enabled) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/definitionProvider.ts b/extensions/markdown-language-features/src/languageFeatures/definitionProvider.ts deleted file mode 100644 index 622d6bf87a6..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/definitionProvider.ts +++ /dev/null @@ -1,21 +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 vscode from 'vscode'; -import { Disposable } from '../util/dispose'; -import { SkinnyTextDocument } from '../workspaceContents'; -import { MdReferencesProvider } from './references'; - -export class MdDefinitionProvider extends Disposable implements vscode.DefinitionProvider { - - constructor(private readonly referencesProvider: MdReferencesProvider) { - super(); - } - - async provideDefinition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const allRefs = await this.referencesProvider.getAllReferencesAtPosition(document, position, token); - - return allRefs.find(ref => ref.kind === 'link' && ref.isDefinition)?.location; - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index b94ca0f5227..6ae36b84aaf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -5,511 +5,18 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import * as picomatch from 'picomatch'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; -import { Delayer } from '../util/async'; -import { Disposable } from '../util/dispose'; -import { isMarkdownFile } from '../util/file'; -import { Limiter } from '../util/limiter'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider'; -import { tryFindMdDocumentForLink } from './references'; import { CommandManager } from '../commandManager'; -import { ResourceMap } from '../util/resourceMap'; const localize = nls.loadMessageBundle(); -export interface DiagnosticConfiguration { - /** - * Fired when the configuration changes. - */ - readonly onDidChange: vscode.Event; - - getOptions(resource: vscode.Uri): DiagnosticOptions; +// Copied from markdown language service +export enum DiagnosticCode { + link_noSuchReferences = 'link.no-such-reference', + link_noSuchHeaderInOwnFile = 'link.no-such-header-in-own-file', + link_noSuchFile = 'link.no-such-file', + link_noSuchHeaderInFile = 'link.no-such-header-in-file', } -export enum DiagnosticLevel { - ignore = 'ignore', - warning = 'warning', - error = 'error', -} - -export interface DiagnosticOptions { - readonly enabled: boolean; - readonly validateReferences: DiagnosticLevel | undefined; - readonly validateFragmentLinks: DiagnosticLevel | undefined; - readonly validateFileLinks: DiagnosticLevel | undefined; - readonly validateMarkdownFileLinkFragments: DiagnosticLevel | undefined; - readonly ignoreLinks: readonly string[]; -} - -function toSeverity(level: DiagnosticLevel | undefined): vscode.DiagnosticSeverity | undefined { - switch (level) { - case DiagnosticLevel.error: return vscode.DiagnosticSeverity.Error; - case DiagnosticLevel.warning: return vscode.DiagnosticSeverity.Warning; - case DiagnosticLevel.ignore: return undefined; - case undefined: return undefined; - } -} - -class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConfiguration { - - private readonly _onDidChange = this._register(new vscode.EventEmitter()); - public readonly onDidChange = this._onDidChange.event; - - constructor() { - super(); - - this._register(vscode.workspace.onDidChangeConfiguration(e => { - if ( - e.affectsConfiguration('markdown.experimental.validate.enabled') - || e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fragmentLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.markdownFragmentLinks') - || e.affectsConfiguration('markdown.experimental.validate.ignoreLinks') - ) { - this._onDidChange.fire(); - } - })); - } - - public getOptions(resource: vscode.Uri): DiagnosticOptions { - const config = vscode.workspace.getConfiguration('markdown', resource); - const validateFragmentLinks = config.get('experimental.validate.fragmentLinks.enabled'); - return { - enabled: config.get('experimental.validate.enabled', false), - validateReferences: config.get('experimental.validate.referenceLinks.enabled'), - validateFragmentLinks, - validateFileLinks: config.get('experimental.validate.fileLinks.enabled'), - validateMarkdownFileLinkFragments: config.get('markdown.experimental.validate.fileLinks.markdownFragmentLinks', validateFragmentLinks), - ignoreLinks: config.get('experimental.validate.ignoreLinks', []), - }; - } -} - -class InflightDiagnosticRequests { - - private readonly inFlightRequests = new ResourceMap<{ readonly cts: vscode.CancellationTokenSource }>(); - - public trigger(resource: vscode.Uri, compute: (token: vscode.CancellationToken) => Promise) { - this.cancel(resource); - - const cts = new vscode.CancellationTokenSource(); - const entry = { cts }; - this.inFlightRequests.set(resource, entry); - - compute(cts.token).finally(() => { - if (this.inFlightRequests.get(resource) === entry) { - this.inFlightRequests.delete(resource); - } - cts.dispose(); - }); - } - - public cancel(resource: vscode.Uri) { - const existing = this.inFlightRequests.get(resource); - if (existing) { - existing.cts.cancel(); - this.inFlightRequests.delete(resource); - } - } - - public dispose() { - this.clear(); - } - - public clear() { - for (const { cts } of this.inFlightRequests.values()) { - cts.dispose(); - } - this.inFlightRequests.clear(); - } -} - -class LinkWatcher extends Disposable { - - private readonly _onDidChangeLinkedToFile = this._register(new vscode.EventEmitter>); - /** - * Event fired with a list of document uri when one of the links in the document changes - */ - public readonly onDidChangeLinkedToFile = this._onDidChangeLinkedToFile.event; - - private readonly _watchers = new Map; - }>(); - - override dispose() { - super.dispose(); - - for (const entry of this._watchers.values()) { - entry.watcher.dispose(); - } - this._watchers.clear(); - } - - /** - * Set the known links in a markdown document, adding and removing file watchers as needed - */ - updateLinksForDocument(document: vscode.Uri, links: readonly MdLink[]) { - const linkedToResource = new Set( - links - .filter(link => link.href.kind === 'internal') - .map(link => (link.href as InternalHref).path)); - - // First decrement watcher counter for previous document state - for (const entry of this._watchers.values()) { - entry.documents.delete(document.toString()); - } - - // Then create/update watchers for new document state - for (const path of linkedToResource) { - let entry = this._watchers.get(path.toString()); - if (!entry) { - entry = { - watcher: this.startWatching(path), - documents: new Map(), - }; - this._watchers.set(path.toString(), entry); - } - - entry.documents.set(document.toString(), document); - } - - // Finally clean up watchers for links that are no longer are referenced anywhere - for (const [key, value] of this._watchers) { - if (value.documents.size === 0) { - value.watcher.dispose(); - this._watchers.delete(key); - } - } - } - - deleteDocument(resource: vscode.Uri) { - this.updateLinksForDocument(resource, []); - } - - private startWatching(path: vscode.Uri): vscode.Disposable { - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path, '*'), false, true, false); - const handler = (resource: vscode.Uri) => this.onLinkedResourceChanged(resource); - return vscode.Disposable.from( - watcher, - watcher.onDidDelete(handler), - watcher.onDidCreate(handler), - ); - } - - private onLinkedResourceChanged(resource: vscode.Uri) { - const entry = this._watchers.get(resource.toString()); - if (entry) { - this._onDidChangeLinkedToFile.fire(entry.documents.values()); - } - } -} - -class LinkDoesNotExistDiagnostic extends vscode.Diagnostic { - - public readonly link: string; - - constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, link: string) { - super(range, message, severity); - this.link = link; - } -} - -export class DiagnosticManager extends Disposable { - - private readonly collection: vscode.DiagnosticCollection; - - private readonly diagnosticDelayer: Delayer; - private readonly pendingDiagnostics = new Set(); - private readonly inFlightDiagnostics = this._register(new InflightDiagnosticRequests()); - - private readonly linkWatcher = this._register(new LinkWatcher()); - - constructor( - private readonly computer: DiagnosticComputer, - private readonly configuration: DiagnosticConfiguration, - ) { - super(); - - this.diagnosticDelayer = this._register(new Delayer(300)); - - this.collection = this._register(vscode.languages.createDiagnosticCollection('markdown')); - - this._register(this.configuration.onDidChange(() => { - this.rebuild(); - })); - - this._register(vscode.workspace.onDidOpenTextDocument(doc => { - this.triggerDiagnostics(doc); - })); - - this._register(vscode.workspace.onDidChangeTextDocument(e => { - this.triggerDiagnostics(e.document); - })); - - this._register(vscode.workspace.onDidCloseTextDocument(({ uri }) => { - this.pendingDiagnostics.delete(uri); - this.inFlightDiagnostics.cancel(uri); - this.linkWatcher.deleteDocument(uri); - this.collection.delete(uri); - })); - - this._register(this.linkWatcher.onDidChangeLinkedToFile(changedDocuments => { - for (const resource of changedDocuments) { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString()); - if (doc) { - this.triggerDiagnostics(doc); - } - } - })); - - this.rebuild(); - } - - public override dispose() { - super.dispose(); - this.pendingDiagnostics.clear(); - } - - public async recomputeDiagnosticState(doc: SkinnyTextDocument, token: vscode.CancellationToken): Promise<{ diagnostics: readonly vscode.Diagnostic[]; links: readonly MdLink[]; config: DiagnosticOptions }> { - const config = this.configuration.getOptions(doc.uri); - if (!config.enabled) { - return { diagnostics: [], links: [], config }; - } - return { ...await this.computer.getDiagnostics(doc, config, token), config }; - } - - private async recomputePendingDiagnostics(): Promise { - const pending = [...this.pendingDiagnostics]; - this.pendingDiagnostics.clear(); - - for (const resource of pending) { - const doc = vscode.workspace.textDocuments.find(doc => doc.uri.fsPath === resource.fsPath); - if (doc) { - this.inFlightDiagnostics.trigger(doc.uri, async (token) => { - const state = await this.recomputeDiagnosticState(doc, token); - this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []); - this.collection.set(doc.uri, state.diagnostics); - }); - } - } - } - - private async rebuild() { - this.collection.clear(); - this.pendingDiagnostics.clear(); - this.inFlightDiagnostics.clear(); - - const allOpenedTabResources = this.getAllTabResources(); - await Promise.all( - vscode.workspace.textDocuments - .filter(doc => allOpenedTabResources.has(doc.uri) && isMarkdownFile(doc)) - .map(doc => this.triggerDiagnostics(doc))); - } - - private getAllTabResources(): ResourceMap { - const openedTabDocs = new ResourceMap(); - for (const group of vscode.window.tabGroups.all) { - for (const tab of group.tabs) { - if (tab.input instanceof vscode.TabInputText) { - openedTabDocs.set(tab.input.uri); - } - } - } - return openedTabDocs; - } - - private triggerDiagnostics(doc: vscode.TextDocument) { - this.inFlightDiagnostics.cancel(doc.uri); - - if (isMarkdownFile(doc)) { - this.pendingDiagnostics.add(doc.uri); - this.diagnosticDelayer.trigger(() => this.recomputePendingDiagnostics()); - } - } -} - -interface FileLinksData { - readonly path: vscode.Uri; - - readonly links: Array<{ - readonly source: MdLinkSource; - readonly fragment: string; - }>; -} - -/** - * Map of file paths to markdown links to that file. - */ -class FileLinkMap { - - private readonly _filesToLinksMap = new ResourceMap(); - - constructor(links: Iterable) { - for (const link of links) { - if (link.href.kind !== 'internal') { - continue; - } - - const existingFileEntry = this._filesToLinksMap.get(link.href.path); - const linkData = { source: link.source, fragment: link.href.fragment }; - if (existingFileEntry) { - existingFileEntry.links.push(linkData); - } else { - this._filesToLinksMap.set(link.href.path, { path: link.href.path, links: [linkData] }); - } - } - } - - public get size(): number { - return this._filesToLinksMap.size; - } - - public entries(): Iterable { - return this._filesToLinksMap.values(); - } -} - -export class DiagnosticComputer { - - constructor( - private readonly engine: MarkdownEngine, - private readonly workspaceContents: MdWorkspaceContents, - private readonly linkProvider: MdLinkProvider, - ) { } - - public async getDiagnostics(doc: SkinnyTextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: MdLink[] }> { - const links = await this.linkProvider.getAllLinks(doc, token); - if (token.isCancellationRequested) { - return { links, diagnostics: [] }; - } - - return { - links, - diagnostics: (await Promise.all([ - this.validateFileLinks(doc, options, links, token), - Array.from(this.validateReferenceLinks(options, links)), - this.validateFragmentLinks(doc, options, links, token), - ])).flat() - }; - } - - private async validateFragmentLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise { - const severity = toSeverity(options.validateFragmentLinks); - if (typeof severity === 'undefined') { - return []; - } - - const toc = await TableOfContents.create(this.engine, doc); - if (token.isCancellationRequested) { - return []; - } - - const diagnostics: vscode.Diagnostic[] = []; - for (const link of links) { - if (link.href.kind === 'internal' - && link.href.path.toString() === doc.uri.toString() - && link.href.fragment - && !toc.lookup(link.href.fragment) - ) { - if (!this.isIgnoredLink(options, link.source.text)) { - diagnostics.push(new LinkDoesNotExistDiagnostic( - link.source.hrefRange, - localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), - severity, - link.source.text)); - } - } - } - - return diagnostics; - } - - private *validateReferenceLinks(options: DiagnosticOptions, links: readonly MdLink[]): Iterable { - const severity = toSeverity(options.validateReferences); - if (typeof severity === 'undefined') { - return []; - } - - const definitionSet = new LinkDefinitionSet(links); - for (const link of links) { - if (link.href.kind === 'reference' && !definitionSet.lookup(link.href.ref)) { - yield new vscode.Diagnostic( - link.source.hrefRange, - localize('invalidReferenceLink', 'No link definition found: \'{0}\'', link.href.ref), - severity); - } - } - } - - private async validateFileLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise { - const pathErrorSeverity = toSeverity(options.validateFileLinks); - if (typeof pathErrorSeverity === 'undefined') { - return []; - } - const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments); - - const linkSet = new FileLinkMap(links); - if (linkSet.size === 0) { - return []; - } - - const limiter = new Limiter(10); - - const diagnostics: vscode.Diagnostic[] = []; - await Promise.all( - Array.from(linkSet.entries()).map(({ path, links }) => { - return limiter.queue(async () => { - if (token.isCancellationRequested) { - return; - } - - const hrefDoc = await tryFindMdDocumentForLink({ kind: 'internal', path: path, fragment: '' }, this.workspaceContents); - if (hrefDoc && hrefDoc.uri.toString() === doc.uri.toString()) { - // We've already validated our own links in `validateOwnHeaderLinks` - return; - } - - if (!hrefDoc && !await this.workspaceContents.pathExists(path)) { - const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath); - for (const link of links) { - if (!this.isIgnoredLink(options, link.source.pathText)) { - diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, pathErrorSeverity, link.source.pathText)); - } - } - } else if (hrefDoc && typeof fragmentErrorSeverity !== 'undefined') { - // Validate each of the links to headers in the file - const fragmentLinks = links.filter(x => x.fragment); - if (fragmentLinks.length) { - const toc = await TableOfContents.create(this.engine, hrefDoc); - for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) { - const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); - diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, fragmentErrorSeverity, link.source.text)); - } - } - } - } - }); - })); - return diagnostics; - } - - private isIgnoredLink(options: DiagnosticOptions, link: string): boolean { - return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob)); - } -} class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { @@ -540,17 +47,26 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { const fixes: vscode.CodeAction[] = []; for (const diagnostic of context.diagnostics) { - if (diagnostic instanceof LinkDoesNotExistDiagnostic) { - const fix = new vscode.CodeAction( - localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", diagnostic.link), - vscode.CodeActionKind.QuickFix); + switch (diagnostic.code) { + case DiagnosticCode.link_noSuchReferences: + case DiagnosticCode.link_noSuchHeaderInOwnFile: + case DiagnosticCode.link_noSuchFile: + case DiagnosticCode.link_noSuchHeaderInFile: { + const hrefText = (diagnostic as any).data?.hrefText; + if (hrefText) { + const fix = new vscode.CodeAction( + localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", hrefText), + vscode.CodeActionKind.QuickFix); - fix.command = { - command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, - title: '', - arguments: [document.uri, diagnostic.link] - }; - fixes.push(fix); + fix.command = { + command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, + title: '', + arguments: [document.uri, hrefText], + }; + fixes.push(fix); + } + break; + } } } @@ -558,17 +74,10 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { } } -export function register( + +export function registerDiagnosticSupport( selector: vscode.DocumentSelector, - engine: MarkdownEngine, - workspaceContents: MdWorkspaceContents, - linkProvider: MdLinkProvider, commandManager: CommandManager, ): vscode.Disposable { - const configuration = new VSCodeDiagnosticConfiguration(); - const manager = new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration); - return vscode.Disposable.from( - configuration, - manager, - AddToIgnoreLinksQuickFixProvider.register(selector, commandManager)); + return AddToIgnoreLinksQuickFixProvider.register(selector, commandManager); } diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts deleted file mode 100644 index 337872e596f..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ /dev/null @@ -1,477 +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 vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import * as uri from 'vscode-uri'; -import { OpenDocumentLinkCommand } from '../commands/openDocumentLink'; -import { MarkdownEngine } from '../markdownEngine'; -import { coalesce } from '../util/arrays'; -import { getUriForLinkWithKnownExternalScheme, isOfScheme, Schemes } from '../util/schemes'; -import { SkinnyTextDocument } from '../workspaceContents'; - -const localize = nls.loadMessageBundle(); - -export interface ExternalHref { - readonly kind: 'external'; - readonly uri: vscode.Uri; -} - -export interface InternalHref { - readonly kind: 'internal'; - readonly path: vscode.Uri; - readonly fragment: string; -} - -export interface ReferenceHref { - readonly kind: 'reference'; - readonly ref: string; -} - -export type LinkHref = ExternalHref | InternalHref | ReferenceHref; - - -function parseLink( - document: SkinnyTextDocument, - link: string, -): ExternalHref | InternalHref | undefined { - const cleanLink = stripAngleBrackets(link); - const externalSchemeUri = getUriForLinkWithKnownExternalScheme(cleanLink); - if (externalSchemeUri) { - // Normalize VS Code links to target currently running version - if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) { - return { kind: 'external', uri: vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }) }; - } - return { kind: 'external', uri: externalSchemeUri }; - } - - if (/^[a-z\-][a-z\-]+:/i.test(cleanLink)) { - // Looks like a uri - return { kind: 'external', uri: vscode.Uri.parse(cleanLink) }; - } - - // Assume it must be an relative or absolute file path - // Use a fake scheme to avoid parse warnings - const tempUri = vscode.Uri.parse(`vscode-resource:${link}`); - - let resourceUri: vscode.Uri | undefined; - if (!tempUri.path) { - resourceUri = document.uri; - } else if (tempUri.path[0] === '/') { - const root = getWorkspaceFolder(document); - if (root) { - resourceUri = vscode.Uri.joinPath(root, tempUri.path); - } - } else { - if (document.uri.scheme === Schemes.untitled) { - const root = getWorkspaceFolder(document); - if (root) { - resourceUri = vscode.Uri.joinPath(root, tempUri.path); - } - } else { - const base = uri.Utils.dirname(document.uri); - resourceUri = vscode.Uri.joinPath(base, tempUri.path); - } - } - - if (!resourceUri) { - return undefined; - } - - return { - kind: 'internal', - path: resourceUri.with({ fragment: '' }), - fragment: tempUri.fragment, - }; -} - -function getWorkspaceFolder(document: SkinnyTextDocument) { - return vscode.workspace.getWorkspaceFolder(document.uri)?.uri - || vscode.workspace.workspaceFolders?.[0]?.uri; -} - -export interface MdLinkSource { - /** - * The original text of the link destination in code. - */ - readonly text: string; - - /** - * The original text of just the link's path in code. - */ - readonly pathText: string; - - readonly resource: vscode.Uri; - readonly hrefRange: vscode.Range; - readonly fragmentRange: vscode.Range | undefined; -} - -export interface MdInlineLink { - readonly kind: 'link'; - readonly source: MdLinkSource; - readonly href: LinkHref; -} - -export interface MdLinkDefinition { - readonly kind: 'definition'; - readonly source: MdLinkSource; - readonly ref: { - readonly range: vscode.Range; - readonly text: string; - }; - readonly href: ExternalHref | InternalHref; -} - -export type MdLink = MdInlineLink | MdLinkDefinition; - -function extractDocumentLink( - document: SkinnyTextDocument, - pre: number, - link: string, - matchIndex: number | undefined -): MdLink | undefined { - const offset = (matchIndex || 0) + pre; - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); - try { - const linkTarget = parseLink(document, link); - if (!linkTarget) { - return undefined; - } - return { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; - } catch { - return undefined; - } -} - -function getFragmentRange(text: string, start: vscode.Position, end: vscode.Position): vscode.Range | undefined { - const index = text.indexOf('#'); - if (index < 0) { - return undefined; - } - return new vscode.Range(start.translate({ characterDelta: index + 1 }), end); -} - -function getLinkSourceFragmentInfo(document: SkinnyTextDocument, link: string, linkStart: vscode.Position, linkEnd: vscode.Position): { fragmentRange: vscode.Range | undefined; pathText: string } { - const fragmentRange = getFragmentRange(link, linkStart, linkEnd); - return { - pathText: document.getText(new vscode.Range(linkStart, fragmentRange ? fragmentRange.start.translate(0, -1) : linkEnd)), - fragmentRange, - }; -} - -const angleBracketLinkRe = /^<(.*)>$/; - -/** - * Used to strip brackets from the markdown link - * - * will be transformed to http://example.com -*/ -function stripAngleBrackets(link: string) { - return link.replace(angleBracketLinkRe, '$1'); -} - -/** - * Matches `[text](link)` - */ -const linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]]|\][^(])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g; - -/** - * Matches `[text]()` - */ -const linkPatternAngle = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]]|\][^(])*\])\(\s*<)(([^<>]|\([^\s\(\)]*?\))+)>\s*("[^"]*"|'[^']*'|\([^\(\)]*\))?\s*\)/g; - - -/** - * Matches `[text][ref]` or `[shorthand]` - */ -const referenceLinkPattern = /(^|[^\]\\])(?:(?:(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]|\[\s*?([^\s\]]*?)\])(?![\:\(]))/gm; - -/** - * Matches `` - */ -const autoLinkPattern = /\<(\w+:[^\>\s]+)\>/g; - -/** - * Matches `[text]: link` - */ -const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm; - -const inlineCodePattern = /(?:^|[^`])(`+)(?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1(?:$|[^`])/gm; - -class NoLinkRanges { - public static async compute(document: SkinnyTextDocument, engine: MarkdownEngine): Promise { - const tokens = await engine.parse(document); - const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map).map(t => t.map) as [number, number][]; - - const text = document.getText(); - const inline = [...text.matchAll(inlineCodePattern)].map(match => { - const start = match.index || 0; - return new vscode.Range(document.positionAt(start), document.positionAt(start + match[0].length)); - }); - - return new NoLinkRanges(multiline, inline); - } - - private constructor( - /** - * code blocks and fences each represented by [line_start,line_end). - */ - public readonly multiline: ReadonlyArray<[number, number]>, - - /** - * Inline code spans where links should not be detected - */ - public readonly inline: readonly vscode.Range[] - ) { } - - contains(range: vscode.Range): boolean { - return this.multiline.some(interval => range.start.line >= interval[0] && range.start.line < interval[1]) || - this.inline.some(position => position.intersection(range)); - } -} - - -export class MdLinkProvider implements vscode.DocumentLinkProvider { - - constructor( - private readonly engine: MarkdownEngine - ) { } - - public async provideDocumentLinks( - document: SkinnyTextDocument, - token: vscode.CancellationToken - ): Promise { - const allLinks = await this.getAllLinks(document, token); - if (token.isCancellationRequested) { - return []; - } - - const definitionSet = new LinkDefinitionSet(allLinks); - return coalesce(allLinks - .map(data => this.toValidDocumentLink(data, definitionSet))); - } - - private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined { - switch (link.href.kind) { - case 'external': { - return new vscode.DocumentLink(link.source.hrefRange, link.href.uri); - } - case 'internal': { - const uri = OpenDocumentLinkCommand.createCommandUri(link.source.resource, link.href.path, link.href.fragment); - const documentLink = new vscode.DocumentLink(link.source.hrefRange, uri); - documentLink.tooltip = localize('documentLink.tooltip', 'Follow link'); - return documentLink; - } - case 'reference': { - const def = definitionSet.lookup(link.href.ref); - if (def) { - const documentLink = new vscode.DocumentLink( - link.source.hrefRange, - vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.source.hrefRange.start.line, def.source.hrefRange.start.character]))}`)); - documentLink.tooltip = localize('documentLink.referenceTooltip', 'Go to link definition'); - return documentLink; - } else { - return undefined; - } - } - } - } - - public async getAllLinks(document: SkinnyTextDocument, token: vscode.CancellationToken): Promise { - const noLinkRanges = await NoLinkRanges.compute(document, this.engine); - if (token.isCancellationRequested) { - return []; - } - - return Array.from([ - ...this.getInlineLinks(document, noLinkRanges), - ...this.getReferenceLinks(document, noLinkRanges), - ...this.getLinkDefinitions2(document, noLinkRanges), - ...this.getAutoLinks(document, noLinkRanges), - ]); - } - - private *getInlineLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - - for (const match of text.matchAll(linkPatternAngle)) { - const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); - if (matchImageData && !noLinkRanges.contains(matchImageData.source.hrefRange)) { - yield matchImageData; - } - const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index); - if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange)) { - yield matchLinkData; - } - } - - for (const match of text.matchAll(linkPattern)) { - const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index); - if (matchImageData && !noLinkRanges.contains(matchImageData.source.hrefRange)) { - yield matchImageData; - } - - if (match[5] !== undefined && match[5].startsWith('<')) { - continue; - } - - const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index); - if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange)) { - yield matchLinkData; - } - } - } - - private *getAutoLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - - for (const match of text.matchAll(autoLinkPattern)) { - const link = match[1]; - const linkTarget = parseLink(document, link); - if (linkTarget) { - const offset = (match.index ?? 0) + 1; - const linkStart = document.positionAt(offset); - const linkEnd = document.positionAt(offset + link.length); - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange)) { - continue; - } - yield { - kind: 'link', - href: linkTarget, - source: { - text: link, - resource: document.uri, - hrefRange: new vscode.Range(linkStart, linkEnd), - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - } - }; - } - } - } - - private *getReferenceLinks(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(referenceLinkPattern)) { - let linkStart: vscode.Position; - let linkEnd: vscode.Position; - let reference = match[4]; - if (reference) { // [text][ref] - const pre = match[2]; - const offset = ((match.index ?? 0) + match[1].length) + pre.length; - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + reference.length); - } else if (match[5]) { // [ref][], [ref] - reference = match[5]; - const offset = ((match.index ?? 0) + match[1].length) + 1; - linkStart = document.positionAt(offset); - const line = document.lineAt(linkStart.line); - // See if link looks like a checkbox - const checkboxMatch = line.text.match(/^\s*[\-\*]\s*\[x\]/i); - if (checkboxMatch && linkStart.character <= checkboxMatch[0].length) { - continue; - } - linkEnd = document.positionAt(offset + reference.length); - } else { - continue; - } - - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange)) { - continue; - } - - yield { - kind: 'link', - source: { - text: reference, - pathText: reference, - resource: document.uri, - hrefRange, - fragmentRange: undefined, - }, - href: { - kind: 'reference', - ref: reference, - } - }; - } - } - - public async getLinkDefinitions(document: SkinnyTextDocument): Promise> { - const noLinkRanges = await NoLinkRanges.compute(document, this.engine); - return this.getLinkDefinitions2(document, noLinkRanges); - } - - private *getLinkDefinitions2(document: SkinnyTextDocument, noLinkRanges: NoLinkRanges): Iterable { - const text = document.getText(); - for (const match of text.matchAll(definitionPattern)) { - const pre = match[1]; - const reference = match[2]; - const link = match[3].trim(); - const offset = (match.index || 0) + pre.length; - - const refStart = document.positionAt((match.index ?? 0) + 1); - const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length })); - - let linkStart: vscode.Position; - let linkEnd: vscode.Position; - let text: string; - if (angleBracketLinkRe.test(link)) { - linkStart = document.positionAt(offset + 1); - linkEnd = document.positionAt(offset + link.length - 1); - text = link.substring(1, link.length - 1); - } else { - linkStart = document.positionAt(offset); - linkEnd = document.positionAt(offset + link.length); - text = link; - } - const hrefRange = new vscode.Range(linkStart, linkEnd); - if (noLinkRanges.contains(hrefRange)) { - continue; - } - const target = parseLink(document, text); - if (target) { - yield { - kind: 'definition', - source: { - text: link, - resource: document.uri, - hrefRange, - ...getLinkSourceFragmentInfo(document, link, linkStart, linkEnd), - }, - ref: { text: reference, range: refRange }, - href: target, - }; - } - } - } -} - -export class LinkDefinitionSet { - private readonly _map = new Map(); - - constructor(links: Iterable) { - for (const link of links) { - if (link.kind === 'definition') { - this._map.set(link.ref.text, link); - } - } - } - - public lookup(ref: string): MdLinkDefinition | undefined { - return this._map.get(ref); - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts deleted file mode 100644 index cdb1b8a79b8..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/documentSymbolProvider.ts +++ /dev/null @@ -1,76 +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 vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents, TocEntry } from '../tableOfContents'; -import { SkinnyTextDocument } from '../workspaceContents'; - -interface MarkdownSymbol { - readonly level: number; - readonly parent: MarkdownSymbol | undefined; - readonly children: vscode.DocumentSymbol[]; -} - -export class MdDocumentSymbolProvider implements vscode.DocumentSymbolProvider { - - constructor( - private readonly engine: MarkdownEngine - ) { } - - public async provideDocumentSymbolInformation(document: SkinnyTextDocument): Promise { - const toc = await TableOfContents.create(this.engine, document); - return toc.entries.map(entry => this.toSymbolInformation(entry)); - } - - public async provideDocumentSymbols(document: SkinnyTextDocument): Promise { - const toc = await TableOfContents.create(this.engine, document); - const root: MarkdownSymbol = { - level: -Infinity, - children: [], - parent: undefined - }; - this.buildTree(root, toc.entries); - return root.children; - } - - private buildTree(parent: MarkdownSymbol, entries: readonly TocEntry[]) { - if (!entries.length) { - return; - } - - const entry = entries[0]; - const symbol = this.toDocumentSymbol(entry); - symbol.children = []; - - while (parent && entry.level <= parent.level) { - parent = parent.parent!; - } - parent.children.push(symbol); - this.buildTree({ level: entry.level, children: symbol.children, parent }, entries.slice(1)); - } - - - private toSymbolInformation(entry: TocEntry): vscode.SymbolInformation { - return new vscode.SymbolInformation( - this.getSymbolName(entry), - vscode.SymbolKind.String, - '', - entry.sectionLocation); - } - - private toDocumentSymbol(entry: TocEntry) { - return new vscode.DocumentSymbol( - this.getSymbolName(entry), - '', - vscode.SymbolKind.String, - entry.sectionLocation.range, - entry.sectionLocation.range); - } - - private getSymbolName(entry: TocEntry): string { - return '#'.repeat(entry.level) + ' ' + entry.text; - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index 7217db35652..fa7f8f5d2c8 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as URI from 'vscode-uri'; +import { Schemes } from '../util/schemes'; const imageFileExtensions = new Set([ '.bmp', @@ -23,9 +24,9 @@ const imageFileExtensions = new Set([ '.webp', ]); -export function registerDropIntoEditor(selector: vscode.DocumentSelector) { - return vscode.languages.registerDocumentOnDropEditProvider(selector, new class implements vscode.DocumentOnDropEditProvider { - async provideDocumentOnDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { +export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) { + return vscode.languages.registerDocumentDropEditProvider(selector, new class implements vscode.DocumentDropEditProvider { + async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); if (!enabled) { return undefined; @@ -56,10 +57,12 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr return; } + const docUri = getParentDocumentUri(document); + const snippet = new vscode.SnippetString(); uris.forEach((uri, i) => { - const mdPath = document.uri.scheme === uri.scheme - ? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/')) + const mdPath = docUri.scheme === uri.scheme && docUri.authority === uri.authority + ? encodeURI(path.relative(URI.Utils.dirname(docUri).fsPath, uri.fsPath).replace(/\\/g, '/')) : uri.toString(false); const ext = URI.Utils.extname(uri).toLowerCase(); @@ -74,3 +77,17 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr return snippet; } + +function getParentDocumentUri(document: vscode.TextDocument): vscode.Uri { + if (document.uri.scheme === Schemes.notebookCell) { + for (const notebook of vscode.workspace.notebookDocuments) { + for (const cell of notebook.getCells()) { + if (cell.document === document) { + return notebook.uri; + } + } + } + } + + return document.uri; +} diff --git a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts index b9f1321cf06..6d8b5eab079 100644 --- a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts +++ b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts @@ -4,9 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { BaseLanguageClient } from 'vscode-languageclient'; +import type * as lsp from 'vscode-languageserver-types'; import * as nls from 'vscode-nls'; import { Command, CommandManager } from '../commandManager'; -import { MdReferencesProvider } from './references'; +import { getReferencesToFileInWorkspace } from '../protocol'; const localize = nls.loadMessageBundle(); @@ -16,7 +18,7 @@ export class FindFileReferencesCommand implements Command { public readonly id = 'markdown.findAllFileReferences'; constructor( - private readonly referencesProvider: MdReferencesProvider, + private readonly client: BaseLanguageClient, ) { } public async execute(resource?: vscode.Uri) { @@ -33,8 +35,9 @@ export class FindFileReferencesCommand implements Command { location: vscode.ProgressLocation.Window, title: localize('progress.title', "Finding file references") }, async (_progress, token) => { - const references = await this.referencesProvider.getAllReferencesToFile(resource!, token); - const locations = references.map(ref => ref.location); + const locations = (await this.client.sendRequest(getReferencesToFileInWorkspace, { uri: resource!.toString() }, token)).map(loc => { + return new vscode.Location(vscode.Uri.parse(loc.uri), convertRange(loc.range)); + }); const config = vscode.workspace.getConfiguration('references'); const existingSetting = config.inspect('preferredLocation'); @@ -49,6 +52,13 @@ export class FindFileReferencesCommand implements Command { } } -export function registerFindFileReferences(commandManager: CommandManager, referencesProvider: MdReferencesProvider): vscode.Disposable { - return commandManager.register(new FindFileReferencesCommand(referencesProvider)); +export function convertRange(range: lsp.Range): vscode.Range { + return new vscode.Range(range.start.line, range.start.character, range.end.line, range.end.character); +} + +export function registerFindFileReferenceSupport( + commandManager: CommandManager, + client: BaseLanguageClient, +): vscode.Disposable { + return commandManager.register(new FindFileReferencesCommand(client)); } diff --git a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts b/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts deleted file mode 100644 index 243a2ee12fd..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/foldingProvider.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import Token = require('markdown-it/lib/token'); -import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; -import { SkinnyTextDocument } from '../workspaceContents'; - -const rangeLimit = 5000; - -interface MarkdownItTokenWithMap extends Token { - map: [number, number]; -} - -export class MdFoldingProvider implements vscode.FoldingRangeProvider { - - constructor( - private readonly engine: MarkdownEngine - ) { } - - public async provideFoldingRanges( - document: SkinnyTextDocument, - _: vscode.FoldingContext, - _token: vscode.CancellationToken - ): Promise { - const foldables = await Promise.all([ - this.getRegions(document), - this.getHeaderFoldingRanges(document), - this.getBlockFoldingRanges(document) - ]); - return foldables.flat().slice(0, rangeLimit); - } - - private async getRegions(document: SkinnyTextDocument): Promise { - const tokens = await this.engine.parse(document); - const regionMarkers = tokens.filter(isRegionMarker) - .map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) })); - - const nestingStack: { line: number; isStart: boolean }[] = []; - return regionMarkers - .map(marker => { - if (marker.isStart) { - nestingStack.push(marker); - } else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) { - return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region); - } else { - // noop: invalid nesting (i.e. [end, start] or [start, end, end]) - } - return null; - }) - .filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region); - } - - private async getHeaderFoldingRanges(document: SkinnyTextDocument) { - const toc = await TableOfContents.create(this.engine, document); - return toc.entries.map(entry => { - let endLine = entry.sectionLocation.range.end.line; - if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) { - endLine = endLine - 1; - } - return new vscode.FoldingRange(entry.line, endLine); - }); - } - - private async getBlockFoldingRanges(document: SkinnyTextDocument): Promise { - const tokens = await this.engine.parse(document); - const multiLineListItems = tokens.filter(isFoldableToken); - return multiLineListItems.map(listItem => { - const start = listItem.map[0]; - let end = listItem.map[1] - 1; - if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) { - end = end - 1; - } - return new vscode.FoldingRange(start, end, this.getFoldingRangeKind(listItem)); - }); - } - - private getFoldingRangeKind(listItem: Token): vscode.FoldingRangeKind | undefined { - return listItem.type === 'html_block' && listItem.content.startsWith('/.test(t); -const isEndRegion = (t: string) => /^\s*/.test(t); - -const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap => - !!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content)); - -const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => { - if (!token.map) { - return false; - } - - switch (token.type) { - case 'fence': - case 'list_item_open': - return token.map[1] > token.map[0]; - - case 'html_block': - if (isRegionMarker(token)) { - return false; - } - return token.map[1] > token.map[0] + 1; - - default: - return false; - } -}; diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts deleted file mode 100644 index c4e3b5afe5a..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ /dev/null @@ -1,353 +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 { dirname, resolve } from 'path'; -import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; -import { resolveUriToMarkdownFile } from '../util/openDocumentLink'; -import { SkinnyTextDocument } from '../workspaceContents'; -import { MdLinkProvider } from './documentLinkProvider'; - -enum CompletionContextKind { - /** `[...](|)` */ - Link, - - /** `[...][|]` */ - ReferenceLink, - - /** `[]: |` */ - LinkDefinition, -} - -interface AnchorContext { - /** - * Link text before the `#`. - * - * For `[text](xy#z|abc)` this is `xy`. - */ - readonly beforeAnchor: string; - - /** - * Text of the anchor before the current position. - * - * For `[text](xy#z|abc)` this is `z`. - */ - readonly anchorPrefix: string; -} - -interface CompletionContext { - readonly kind: CompletionContextKind; - - /** - * Text of the link before the current position - * - * For `[text](xy#z|abc)` this is `xy#z`. - */ - readonly linkPrefix: string; - - /** - * Position of the start of the link. - * - * For `[text](xy#z|abc)` this is the position before `xy`. - */ - readonly linkTextStartPosition: vscode.Position; - - /** - * Text of the link after the current position. - * - * For `[text](xy#z|abc)` this is `abc`. - */ - readonly linkSuffix: string; - - /** - * Info if the link looks like it is for an anchor: `[](#header)` - */ - readonly anchorInfo?: AnchorContext; -} - -function tryDecodeUriComponent(str: string): string { - try { - return decodeURIComponent(str); - } catch { - return str; - } -} - -export class MdPathCompletionProvider implements vscode.CompletionItemProvider { - - public static register( - selector: vscode.DocumentSelector, - engine: MarkdownEngine, - linkProvider: MdLinkProvider, - ): vscode.Disposable { - return vscode.languages.registerCompletionItemProvider(selector, new MdPathCompletionProvider(engine, linkProvider), '.', '/', '#'); - } - - constructor( - private readonly engine: MarkdownEngine, - private readonly linkProvider: MdLinkProvider, - ) { } - - public async provideCompletionItems(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise { - if (!this.arePathSuggestionEnabled(document)) { - return []; - } - - const context = this.getPathCompletionContext(document, position); - if (!context) { - return []; - } - - switch (context.kind) { - case CompletionContextKind.ReferenceLink: { - const items: vscode.CompletionItem[] = []; - for await (const item of this.provideReferenceSuggestions(document, position, context)) { - items.push(item); - } - return items; - } - - case CompletionContextKind.LinkDefinition: - case CompletionContextKind.Link: { - const items: vscode.CompletionItem[] = []; - - const isAnchorInCurrentDoc = context.anchorInfo && context.anchorInfo.beforeAnchor.length === 0; - - // Add anchor #links in current doc - if (context.linkPrefix.length === 0 || isAnchorInCurrentDoc) { - const insertRange = new vscode.Range(context.linkTextStartPosition, position); - for await (const item of this.provideHeaderSuggestions(document, position, context, insertRange)) { - items.push(item); - } - } - - if (!isAnchorInCurrentDoc) { - if (context.anchorInfo) { // Anchor to a different document - const rawUri = this.resolveReference(document, context.anchorInfo.beforeAnchor); - if (rawUri) { - const otherDoc = await resolveUriToMarkdownFile(rawUri); - if (otherDoc) { - const anchorStartPosition = position.translate({ characterDelta: -(context.anchorInfo.anchorPrefix.length + 1) }); - const range = new vscode.Range(anchorStartPosition, position); - for await (const item of this.provideHeaderSuggestions(otherDoc, position, context, range)) { - items.push(item); - } - } - } - } else { // Normal path suggestions - for await (const item of this.providePathSuggestions(document, position, context)) { - items.push(item); - } - } - } - - return items; - } - } - } - - private arePathSuggestionEnabled(document: SkinnyTextDocument): boolean { - const config = vscode.workspace.getConfiguration('markdown', document.uri); - return config.get('suggest.paths.enabled', true); - } - - /// [...](...| - private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*([^\s\(\)]*)$/; - - /// [...][...| - private readonly referenceLinkStartPattern = /\[([^\]]*?)\]\[\s*([^\s\(\)]*)$/; - - /// [id]: | - private readonly definitionPattern = /^\s*\[[\w\-]+\]:\s*([^\s]*)$/m; - - private getPathCompletionContext(document: SkinnyTextDocument, position: vscode.Position): CompletionContext | undefined { - const line = document.lineAt(position.line).text; - - const linePrefixText = line.slice(0, position.character); - const lineSuffixText = line.slice(position.character); - - const linkPrefixMatch = linePrefixText.match(this.linkStartPattern); - if (linkPrefixMatch) { - const prefix = linkPrefixMatch[2]; - if (this.refLooksLikeUrl(prefix)) { - return undefined; - } - - const suffix = lineSuffixText.match(/^[^\)\s]*/); - return { - kind: CompletionContextKind.Link, - linkPrefix: tryDecodeUriComponent(prefix), - linkTextStartPosition: position.translate({ characterDelta: -prefix.length }), - linkSuffix: suffix ? suffix[0] : '', - anchorInfo: this.getAnchorContext(prefix), - }; - } - - const definitionLinkPrefixMatch = linePrefixText.match(this.definitionPattern); - if (definitionLinkPrefixMatch) { - const prefix = definitionLinkPrefixMatch[1]; - if (this.refLooksLikeUrl(prefix)) { - return undefined; - } - - const suffix = lineSuffixText.match(/^[^\s]*/); - return { - kind: CompletionContextKind.LinkDefinition, - linkPrefix: tryDecodeUriComponent(prefix), - linkTextStartPosition: position.translate({ characterDelta: -prefix.length }), - linkSuffix: suffix ? suffix[0] : '', - anchorInfo: this.getAnchorContext(prefix), - }; - } - - const referenceLinkPrefixMatch = linePrefixText.match(this.referenceLinkStartPattern); - if (referenceLinkPrefixMatch) { - const prefix = referenceLinkPrefixMatch[2]; - const suffix = lineSuffixText.match(/^[^\]\s]*/); - return { - kind: CompletionContextKind.ReferenceLink, - linkPrefix: prefix, - linkTextStartPosition: position.translate({ characterDelta: -prefix.length }), - linkSuffix: suffix ? suffix[0] : '', - }; - } - - return undefined; - } - - /** - * Check if {@param ref} looks like a 'http:' style url. - */ - private refLooksLikeUrl(prefix: string): boolean { - return /^\s*[\w\d\-]+:/.test(prefix); - } - - private getAnchorContext(prefix: string): AnchorContext | undefined { - const anchorMatch = prefix.match(/^(.*)#([\w\d\-]*)$/); - if (!anchorMatch) { - return undefined; - } - return { - beforeAnchor: anchorMatch[1], - anchorPrefix: anchorMatch[2], - }; - } - - private async *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable { - const insertionRange = new vscode.Range(context.linkTextStartPosition, position); - const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length })); - - const definitions = await this.linkProvider.getLinkDefinitions(document); - for (const def of definitions) { - yield { - kind: vscode.CompletionItemKind.Reference, - label: def.ref.text, - range: { - inserting: insertionRange, - replacing: replacementRange, - }, - }; - } - } - - private async *provideHeaderSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext, insertionRange: vscode.Range): AsyncIterable { - const toc = await TableOfContents.createForDocumentOrNotebook(this.engine, document); - for (const entry of toc.entries) { - const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length })); - yield { - kind: vscode.CompletionItemKind.Reference, - label: '#' + decodeURIComponent(entry.slug.value), - range: { - inserting: insertionRange, - replacing: replacementRange, - }, - }; - } - } - - private async *providePathSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable { - const valueBeforeLastSlash = context.linkPrefix.substring(0, context.linkPrefix.lastIndexOf('/') + 1); // keep the last slash - - const parentDir = this.resolveReference(document, valueBeforeLastSlash || '.'); - if (!parentDir) { - return; - } - - const pathSegmentStart = position.translate({ characterDelta: valueBeforeLastSlash.length - context.linkPrefix.length }); - const insertRange = new vscode.Range(pathSegmentStart, position); - - const pathSegmentEnd = position.translate({ characterDelta: context.linkSuffix.length }); - const replacementRange = new vscode.Range(pathSegmentStart, pathSegmentEnd); - - let dirInfo: Array<[string, vscode.FileType]>; - try { - dirInfo = await vscode.workspace.fs.readDirectory(parentDir); - } catch { - return; - } - - for (const [name, type] of dirInfo) { - // Exclude paths that start with `.` - if (name.startsWith('.')) { - continue; - } - - const isDir = type === vscode.FileType.Directory; - yield { - label: isDir ? name + '/' : name, - insertText: isDir ? encodeURIComponent(name) + '/' : encodeURIComponent(name), - kind: isDir ? vscode.CompletionItemKind.Folder : vscode.CompletionItemKind.File, - range: { - inserting: insertRange, - replacing: replacementRange, - }, - command: isDir ? { command: 'editor.action.triggerSuggest', title: '' } : undefined, - }; - } - } - - private resolveReference(document: SkinnyTextDocument, ref: string): vscode.Uri | undefined { - const docUri = this.getFileUriOfTextDocument(document); - - if (ref.startsWith('/')) { - const workspaceFolder = vscode.workspace.getWorkspaceFolder(docUri); - if (workspaceFolder) { - return vscode.Uri.joinPath(workspaceFolder.uri, ref); - } else { - return this.resolvePath(docUri, ref.slice(1)); - } - } - - return this.resolvePath(docUri, ref); - } - - private resolvePath(root: vscode.Uri, ref: string): vscode.Uri | undefined { - try { - if (root.scheme === 'file') { - return vscode.Uri.file(resolve(dirname(root.fsPath), ref)); - } else { - return root.with({ - path: resolve(dirname(root.path), ref), - }); - } - } catch { - return undefined; - } - } - - private getFileUriOfTextDocument(document: SkinnyTextDocument) { - if (document.uri.scheme === 'vscode-notebook-cell') { - const notebook = vscode.workspace.notebookDocuments - .find(notebook => notebook.getCells().some(cell => cell.document === document)); - - if (notebook) { - return notebook.uri; - } - } - - return document.uri; - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts deleted file mode 100644 index 5f63973d701..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ /dev/null @@ -1,308 +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 vscode from 'vscode'; -import * as uri from 'vscode-uri'; -import { MarkdownEngine } from '../markdownEngine'; -import { Slugifier } from '../slugify'; -import { TableOfContents, TocEntry } from '../tableOfContents'; -import { noopToken } from '../util/cancellation'; -import { Disposable } from '../util/dispose'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { InternalHref, MdLink, MdLinkProvider } from './documentLinkProvider'; -import { MdWorkspaceCache } from './workspaceCache'; - - -/** - * A link in a markdown file. - */ -export interface MdLinkReference { - readonly kind: 'link'; - readonly isTriggerLocation: boolean; - readonly isDefinition: boolean; - readonly location: vscode.Location; - - readonly link: MdLink; -} - -/** - * A header in a markdown file. - */ -export interface MdHeaderReference { - readonly kind: 'header'; - - readonly isTriggerLocation: boolean; - readonly isDefinition: boolean; - - /** - * The range of the header. - * - * In `# a b c #` this would be the range of `# a b c #` - */ - readonly location: vscode.Location; - - /** - * The text of the header. - * - * In `# a b c #` this would be `a b c` - */ - readonly headerText: string; - - /** - * The range of the header text itself. - * - * In `# a b c #` this would be the range of `a b c` - */ - readonly headerTextLocation: vscode.Location; -} - -export type MdReference = MdLinkReference | MdHeaderReference; - -export class MdReferencesProvider extends Disposable implements vscode.ReferenceProvider { - - private readonly _linkCache: MdWorkspaceCache; - - public constructor( - private readonly linkProvider: MdLinkProvider, - private readonly workspaceContents: MdWorkspaceContents, - private readonly engine: MarkdownEngine, - private readonly slugifier: Slugifier, - ) { - super(); - - this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkProvider.getAllLinks(doc, noopToken))); - } - - async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise { - const allRefs = await this.getAllReferencesAtPosition(document, position, token); - - return allRefs - .filter(ref => context.includeDeclaration || !ref.isDefinition) - .map(ref => ref.location); - } - - public async getAllReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const toc = await TableOfContents.create(this.engine, document); - if (token.isCancellationRequested) { - return []; - } - - const header = toc.entries.find(entry => entry.line === position.line); - if (header) { - return this.getReferencesToHeader(document, header); - } else { - return this.getReferencesToLinkAtPosition(document, position, token); - } - } - - private async getReferencesToHeader(document: SkinnyTextDocument, header: TocEntry): Promise { - const links = (await this._linkCache.values()).flat(); - - const references: MdReference[] = []; - - references.push({ - kind: 'header', - isTriggerLocation: true, - isDefinition: true, - location: header.headerLocation, - headerText: header.text, - headerTextLocation: header.headerTextLocation - }); - - for (const link of links) { - if (link.href.kind === 'internal' - && this.looksLikeLinkToDoc(link.href, document.uri) - && this.slugifier.fromHeading(link.href.fragment).value === header.slug.value - ) { - references.push({ - kind: 'link', - isTriggerLocation: false, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - - return references; - } - - private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const docLinks = await this.linkProvider.getAllLinks(document, token); - - for (const link of docLinks) { - if (link.kind === 'definition') { - // We could be in either the ref name or the definition - if (link.ref.range.contains(position)) { - return Array.from(this.getReferencesToLinkReference(docLinks, link.ref.text, { resource: document.uri, range: link.ref.range })); - } else if (link.source.hrefRange.contains(position)) { - return this.getReferencesToLink(link, position, token); - } - } else { - if (link.source.hrefRange.contains(position)) { - return this.getReferencesToLink(link, position, token); - } - } - } - - return []; - } - - private async getReferencesToLink(sourceLink: MdLink, triggerPosition: vscode.Position, token: vscode.CancellationToken): Promise { - const allLinksInWorkspace = (await this._linkCache.values()).flat(); - if (token.isCancellationRequested) { - return []; - } - - if (sourceLink.href.kind === 'reference') { - return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.source.resource, range: sourceLink.source.hrefRange })); - } - - if (sourceLink.href.kind === 'external') { - const references: MdReference[] = []; - - for (const link of allLinksInWorkspace) { - if (link.href.kind === 'external' && link.href.uri.toString() === sourceLink.href.uri.toString()) { - const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - references.push({ - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - return references; - } - - const targetDoc = await tryFindMdDocumentForLink(sourceLink.href, this.workspaceContents); - if (token.isCancellationRequested) { - return []; - } - - const references: MdReference[] = []; - - if (targetDoc && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) { - const toc = await TableOfContents.create(this.engine, targetDoc); - const entry = toc.lookup(sourceLink.href.fragment); - if (entry) { - references.push({ - kind: 'header', - isTriggerLocation: false, - isDefinition: true, - location: entry.headerLocation, - headerText: entry.text, - headerTextLocation: entry.headerTextLocation - }); - } - - for (const link of allLinksInWorkspace) { - if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, targetDoc.uri)) { - continue; - } - - if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) { - const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - references.push({ - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, link.source.hrefRange), - }); - } - } - } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments - references.push(...this.findAllLinksToFile(targetDoc?.uri ?? sourceLink.href.path, allLinksInWorkspace, sourceLink)); - } - - return references; - } - - private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) { - return href.path.fsPath === targetDoc.fsPath - || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath; - } - - public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise { - const allLinksInWorkspace = (await this._linkCache.values()).flat(); - return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined)); - } - - private * findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable { - for (const link of allLinksInWorkspace) { - if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) { - continue; - } - - // Exclude cases where the file is implicitly referencing itself - if (link.source.text.startsWith('#') && link.source.resource.fsPath === resource.fsPath) { - continue; - } - - const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); - const pathRange = this.getPathRange(link); - yield { - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.source.resource, pathRange), - }; - } - } - - private * getReferencesToLinkReference(allLinks: Iterable, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable { - for (const link of allLinks) { - let ref: string; - if (link.kind === 'definition') { - ref = link.ref.text; - } else if (link.href.kind === 'reference') { - ref = link.href.ref; - } else { - continue; - } - - if (ref === refToFind && link.source.resource.fsPath === from.resource.fsPath) { - const isTriggerLocation = from.resource.fsPath === link.source.resource.fsPath && ( - (link.href.kind === 'reference' && from.range.isEqual(link.source.hrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.ref.range))); - - const pathRange = this.getPathRange(link); - yield { - kind: 'link', - isTriggerLocation, - isDefinition: link.kind === 'definition', - link, - location: new vscode.Location(from.resource, pathRange), - }; - } - } - } - - /** - * Get just the range of the file path, dropping the fragment - */ - private getPathRange(link: MdLink): vscode.Range { - return link.source.fragmentRange - ? link.source.hrefRange.with(undefined, link.source.fragmentRange.start.translate(0, -1)) - : link.source.hrefRange; - } -} - -export async function tryFindMdDocumentForLink(href: InternalHref, workspaceContents: MdWorkspaceContents): Promise { - const targetDoc = await workspaceContents.getMarkdownDocument(href.path); - if (targetDoc) { - return targetDoc; - } - - // We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead - if (uri.Utils.extname(href.path) === '') { - const dotMdResource = href.path.with({ path: href.path.path + '.md' }); - return workspaceContents.getMarkdownDocument(dotMdResource); - } - - return undefined; -} - diff --git a/extensions/markdown-language-features/src/languageFeatures/rename.ts b/extensions/markdown-language-features/src/languageFeatures/rename.ts deleted file mode 100644 index 955581b9d97..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/rename.ts +++ /dev/null @@ -1,272 +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 path from 'path'; -import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import * as URI from 'vscode-uri'; -import { Slugifier } from '../slugify'; -import { Disposable } from '../util/dispose'; -import { resolveDocumentLink } from '../util/openDocumentLink'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; -import { InternalHref } from './documentLinkProvider'; -import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryFindMdDocumentForLink } from './references'; - -const localize = nls.loadMessageBundle(); - - -export interface MdReferencesResponse { - references: MdReference[]; - triggerRef: MdReference; -} - -interface MdFileRenameEdit { - readonly from: vscode.Uri; - readonly to: vscode.Uri; -} - -/** - * Type with additional metadata about the edits for testing - * - * This is needed since `vscode.WorkspaceEdit` does not expose info on file renames. - */ -export interface MdWorkspaceEdit { - readonly edit: vscode.WorkspaceEdit; - - readonly fileRenames?: ReadonlyArray; -} - -function tryDecodeUri(str: string): string { - try { - return decodeURI(str); - } catch { - return str; - } -} - -export class MdRenameProvider extends Disposable implements vscode.RenameProvider { - - private cachedRefs?: { - readonly resource: vscode.Uri; - readonly version: number; - readonly position: vscode.Position; - readonly triggerRef: MdReference; - readonly references: MdReference[]; - } | undefined; - - private readonly renameNotSupportedText = localize('invalidRenameLocation', "Rename not supported at location"); - - public constructor( - private readonly referencesProvider: MdReferencesProvider, - private readonly workspaceContents: MdWorkspaceContents, - private readonly slugifier: Slugifier, - ) { - super(); - } - - public async prepareRename(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const allRefsInfo = await this.getAllReferences(document, position, token); - if (token.isCancellationRequested) { - return undefined; - } - - if (!allRefsInfo || !allRefsInfo.references.length) { - throw new Error(this.renameNotSupportedText); - } - - const triggerRef = allRefsInfo.triggerRef; - switch (triggerRef.kind) { - case 'header': { - return { range: triggerRef.headerTextLocation.range, placeholder: triggerRef.headerText }; - } - case 'link': { - if (triggerRef.link.kind === 'definition') { - // We may have been triggered on the ref or the definition itself - if (triggerRef.link.ref.range.contains(position)) { - return { range: triggerRef.link.ref.range, placeholder: triggerRef.link.ref.text }; - } - } - - if (triggerRef.link.href.kind === 'external') { - return { range: triggerRef.link.source.hrefRange, placeholder: document.getText(triggerRef.link.source.hrefRange) }; - } - - // See if we are renaming the fragment or the path - const { fragmentRange } = triggerRef.link.source; - if (fragmentRange?.contains(position)) { - const declaration = this.findHeaderDeclaration(allRefsInfo.references); - if (declaration) { - return { range: fragmentRange, placeholder: declaration.headerText }; - } - return { range: fragmentRange, placeholder: document.getText(fragmentRange) }; - } - - const range = this.getFilePathRange(triggerRef); - if (!range) { - throw new Error(this.renameNotSupportedText); - } - return { range, placeholder: tryDecodeUri(document.getText(range)) }; - } - } - } - - private getFilePathRange(ref: MdLinkReference): vscode.Range { - if (ref.link.source.fragmentRange) { - return ref.link.source.hrefRange.with(undefined, ref.link.source.fragmentRange.start.translate(0, -1)); - } - return ref.link.source.hrefRange; - } - - private findHeaderDeclaration(references: readonly MdReference[]): MdHeaderReference | undefined { - return references.find(ref => ref.isDefinition && ref.kind === 'header') as MdHeaderReference | undefined; - } - - public async provideRenameEdits(document: SkinnyTextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise { - return (await this.provideRenameEditsImpl(document, position, newName, token))?.edit; - } - - public async provideRenameEditsImpl(document: SkinnyTextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise { - const allRefsInfo = await this.getAllReferences(document, position, token); - if (token.isCancellationRequested || !allRefsInfo || !allRefsInfo.references.length) { - return undefined; - } - - const triggerRef = allRefsInfo.triggerRef; - - if (triggerRef.kind === 'link' && ( - (triggerRef.link.kind === 'definition' && triggerRef.link.ref.range.contains(position)) || triggerRef.link.href.kind === 'reference' - )) { - return this.renameReferenceLinks(allRefsInfo, newName); - } else if (triggerRef.kind === 'link' && triggerRef.link.href.kind === 'external') { - return this.renameExternalLink(allRefsInfo, newName); - } else if (triggerRef.kind === 'header' || (triggerRef.kind === 'link' && triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'definition' || triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal'))) { - return this.renameFragment(allRefsInfo, newName); - } else if (triggerRef.kind === 'link' && !triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'link' || triggerRef.link.kind === 'definition') && triggerRef.link.href.kind === 'internal') { - return this.renameFilePath(triggerRef.link.source.resource, triggerRef.link.href, allRefsInfo, newName); - } - - return undefined; - } - - private async renameFilePath(triggerDocument: vscode.Uri, triggerHref: InternalHref, allRefsInfo: MdReferencesResponse, newName: string): Promise { - const edit = new vscode.WorkspaceEdit(); - const fileRenames: MdFileRenameEdit[] = []; - - const targetDoc = await tryFindMdDocumentForLink(triggerHref, this.workspaceContents); - const targetUri = targetDoc?.uri ?? triggerHref.path; - - const rawNewFilePath = resolveDocumentLink(newName, triggerDocument); - let resolvedNewFilePath = rawNewFilePath; - if (!URI.Utils.extname(resolvedNewFilePath)) { - // If the newly entered path doesn't have a file extension but the original file did - // tack on a .md file extension - if (URI.Utils.extname(targetUri)) { - resolvedNewFilePath = resolvedNewFilePath.with({ - path: resolvedNewFilePath.path + '.md' - }); - } - } - - // First rename the file - if (await this.workspaceContents.pathExists(targetUri)) { - fileRenames.push({ from: targetUri, to: resolvedNewFilePath }); - edit.renameFile(targetUri, resolvedNewFilePath); - } - - // Then update all refs to it - for (const ref of allRefsInfo.references) { - if (ref.kind === 'link') { - // Try to preserve style of existing links - let newPath: string; - if (ref.link.source.text.startsWith('/')) { - const root = resolveDocumentLink('/', ref.link.source.resource); - newPath = '/' + path.relative(root.toString(true), rawNewFilePath.toString(true)); - } else { - const rootDir = URI.Utils.dirname(ref.link.source.resource); - if (rootDir.scheme === rawNewFilePath.scheme && rootDir.scheme !== 'untitled') { - newPath = path.relative(rootDir.toString(true), rawNewFilePath.toString(true)); - if (newName.startsWith('./') && !newPath.startsWith('../') || newName.startsWith('.\\') && !newPath.startsWith('..\\')) { - newPath = './' + newPath; - } - } else { - newPath = newName; - } - } - edit.replace(ref.link.source.resource, this.getFilePathRange(ref), encodeURI(newPath.replace(/\\/g, '/'))); - } - } - - return { edit, fileRenames }; - } - - private renameFragment(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit { - const slug = this.slugifier.fromHeading(newName).value; - - const edit = new vscode.WorkspaceEdit(); - for (const ref of allRefsInfo.references) { - switch (ref.kind) { - case 'header': - edit.replace(ref.location.uri, ref.headerTextLocation.range, newName); - break; - - case 'link': - edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, !ref.link.source.fragmentRange || ref.link.href.kind === 'external' ? newName : slug); - break; - } - } - return { edit }; - } - - private renameExternalLink(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit { - const edit = new vscode.WorkspaceEdit(); - for (const ref of allRefsInfo.references) { - if (ref.kind === 'link') { - edit.replace(ref.link.source.resource, ref.location.range, newName); - } - } - return { edit }; - } - - private renameReferenceLinks(allRefsInfo: MdReferencesResponse, newName: string): MdWorkspaceEdit { - const edit = new vscode.WorkspaceEdit(); - for (const ref of allRefsInfo.references) { - if (ref.kind === 'link') { - if (ref.link.kind === 'definition') { - edit.replace(ref.link.source.resource, ref.link.ref.range, newName); - } else { - edit.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, newName); - } - } - } - return { edit }; - } - - private async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const version = document.version; - - if (this.cachedRefs - && this.cachedRefs.resource.fsPath === document.uri.fsPath - && this.cachedRefs.version === document.version - && this.cachedRefs.position.isEqual(position) - ) { - return this.cachedRefs; - } - - const references = await this.referencesProvider.getAllReferencesAtPosition(document, position, token); - const triggerRef = references.find(ref => ref.isTriggerLocation); - if (!triggerRef) { - return undefined; - } - - this.cachedRefs = { - resource: document.uri, - version, - position, - references, - triggerRef - }; - return this.cachedRefs; - } -} - diff --git a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts b/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts deleted file mode 100644 index 3f69ea546d7..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/smartSelect.ts +++ /dev/null @@ -1,251 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import Token = require('markdown-it/lib/token'); -import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents, TocEntry } from '../tableOfContents'; -import { SkinnyTextDocument } from '../workspaceContents'; - -interface MarkdownItTokenWithMap extends Token { - map: [number, number]; -} - -export class MdSmartSelect implements vscode.SelectionRangeProvider { - - constructor( - private readonly engine: MarkdownEngine - ) { } - - public async provideSelectionRanges(document: SkinnyTextDocument, positions: vscode.Position[], _token: vscode.CancellationToken): Promise { - const promises = await Promise.all(positions.map((position) => { - return this.provideSelectionRange(document, position, _token); - })); - return promises.filter(item => item !== undefined) as vscode.SelectionRange[]; - } - - private async provideSelectionRange(document: SkinnyTextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise { - const headerRange = await this.getHeaderSelectionRange(document, position); - const blockRange = await this.getBlockSelectionRange(document, position, headerRange); - const inlineRange = await this.getInlineSelectionRange(document, position, blockRange); - return inlineRange || blockRange || headerRange; - } - private async getInlineSelectionRange(document: SkinnyTextDocument, position: vscode.Position, blockRange?: vscode.SelectionRange): Promise { - return createInlineRange(document, position, blockRange); - } - - private async getBlockSelectionRange(document: SkinnyTextDocument, position: vscode.Position, headerRange?: vscode.SelectionRange): Promise { - - const tokens = await this.engine.parse(document); - - const blockTokens = getBlockTokensForPosition(tokens, position, headerRange); - - if (blockTokens.length === 0) { - return undefined; - } - - let currentRange: vscode.SelectionRange | undefined = headerRange ? headerRange : createBlockRange(blockTokens.shift()!, document, position.line); - - for (let i = 0; i < blockTokens.length; i++) { - currentRange = createBlockRange(blockTokens[i], document, position.line, currentRange); - } - return currentRange; - } - - private async getHeaderSelectionRange(document: SkinnyTextDocument, position: vscode.Position): Promise { - const toc = await TableOfContents.create(this.engine, document); - - const headerInfo = getHeadersForPosition(toc.entries, position); - - const headers = headerInfo.headers; - - let currentRange: vscode.SelectionRange | undefined; - - for (let i = 0; i < headers.length; i++) { - currentRange = createHeaderRange(headers[i], i === headers.length - 1, headerInfo.headerOnThisLine, currentRange, getFirstChildHeader(document, headers[i], toc.entries)); - } - return currentRange; - } -} - -function getHeadersForPosition(toc: readonly TocEntry[], position: vscode.Position): { headers: TocEntry[]; headerOnThisLine: boolean } { - const enclosingHeaders = toc.filter(header => header.sectionLocation.range.start.line <= position.line && header.sectionLocation.range.end.line >= position.line); - const sortedHeaders = enclosingHeaders.sort((header1, header2) => (header1.line - position.line) - (header2.line - position.line)); - const onThisLine = toc.find(header => header.line === position.line) !== undefined; - return { - headers: sortedHeaders, - headerOnThisLine: onThisLine - }; -} - -function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean, onHeaderLine: boolean, parent?: vscode.SelectionRange, startOfChildRange?: vscode.Position): vscode.SelectionRange | undefined { - const range = header.sectionLocation.range; - const contentRange = new vscode.Range(range.start.translate(1), range.end); - if (onHeaderLine && isClosestHeaderToPosition && startOfChildRange) { - // selection was made on this header line, so select header and its content until the start of its first child - // then all of its content - return new vscode.SelectionRange(range.with(undefined, startOfChildRange), new vscode.SelectionRange(range, parent)); - } else if (onHeaderLine && isClosestHeaderToPosition) { - // selection was made on this header line and no children so expand to all of its content - return new vscode.SelectionRange(range, parent); - } else if (isClosestHeaderToPosition && startOfChildRange) { - // selection was made within content and has child so select content - // of this header then all content then header - return new vscode.SelectionRange(contentRange.with(undefined, startOfChildRange), new vscode.SelectionRange(contentRange, (new vscode.SelectionRange(range, parent)))); - } else { - // not on this header line so select content then header - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(range, parent)); - } -} - -function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] { - const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token)); - if (enclosingTokens.length === 0) { - return []; - } - const sortedTokens = enclosingTokens.sort((token1, token2) => (token2.map[1] - token2.map[0]) - (token1.map[1] - token1.map[0])); - return sortedTokens; -} - -function createBlockRange(block: MarkdownItTokenWithMap, document: SkinnyTextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - if (block.type === 'fence') { - return createFencedRange(block, cursorLine, document, parent); - } else { - let startLine = document.lineAt(block.map[0]).isEmptyOrWhitespace ? block.map[0] + 1 : block.map[0]; - let endLine = startLine === block.map[1] ? block.map[1] : block.map[1] - 1; - if (block.type === 'paragraph_open' && block.map[1] - block.map[0] === 2) { - startLine = endLine = cursorLine; - } else if (isList(block) && document.lineAt(endLine).isEmptyOrWhitespace) { - endLine = endLine - 1; - } - const range = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text?.length ?? 0); - if (parent?.range.contains(range) && !parent.range.isEqual(range)) { - return new vscode.SelectionRange(range, parent); - } else if (parent?.range.isEqual(range)) { - return parent; - } else { - return new vscode.SelectionRange(range); - } - } -} - -function createInlineRange(document: SkinnyTextDocument, cursorPosition: vscode.Position, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const lineText = document.lineAt(cursorPosition.line).text; - const boldSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, parent); - const italicSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, parent); - let comboSelection: vscode.SelectionRange | undefined; - if (boldSelection && italicSelection && !boldSelection.range.isEqual(italicSelection.range)) { - if (boldSelection.range.contains(italicSelection.range)) { - comboSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, true, boldSelection); - } else if (italicSelection.range.contains(boldSelection.range)) { - comboSelection = createBoldRange(lineText, cursorPosition.character, cursorPosition.line, italicSelection); - } - } - const linkSelection = createLinkRange(lineText, cursorPosition.character, cursorPosition.line, comboSelection || boldSelection || italicSelection || parent); - const inlineCodeBlockSelection = createOtherInlineRange(lineText, cursorPosition.character, cursorPosition.line, false, linkSelection || parent); - return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection; -} - -function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: SkinnyTextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange { - const startLine = token.map[0]; - const endLine = token.map[1] - 1; - const onFenceLine = cursorLine === startLine || cursorLine === endLine; - const fenceRange = new vscode.Range(startLine, 0, endLine, document.lineAt(endLine).text.length); - const contentRange = endLine - startLine > 2 && !onFenceLine ? new vscode.Range(startLine + 1, 0, endLine - 1, document.lineAt(endLine - 1).text.length) : undefined; - if (contentRange) { - return new vscode.SelectionRange(contentRange, new vscode.SelectionRange(fenceRange, parent)); - } else { - if (parent?.range.isEqual(fenceRange)) { - return parent; - } else { - return new vscode.SelectionRange(fenceRange, parent); - } - } -} - -function createBoldRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const regex = /\*\*([^*]+\*?[^*]+\*?[^*]+)\*\*/gim; - const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - if (matches.length) { - // should only be one match, so select first and index 0 contains the entire match - const bold = matches[0][0]; - const startIndex = lineText.indexOf(bold); - const cursorOnStars = cursorChar === startIndex || cursorChar === startIndex + 1 || cursorChar === startIndex + bold.length || cursorChar === startIndex + bold.length - 1; - const contentAndStars = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + bold.length), parent); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 2, cursorLine, startIndex + bold.length - 2), contentAndStars); - return cursorOnStars ? contentAndStars : content; - } - return undefined; -} - -function createOtherInlineRange(lineText: string, cursorChar: number, cursorLine: number, isItalic: boolean, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const italicRegexes = [/(?:[^*]+)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]+)/g, /^(?:[^*]*)(\*([^*]+)(?:\*\*[^*]*\*\*)*([^*]+)\*)(?:[^*]*)$/g]; - let matches = []; - if (isItalic) { - matches = [...lineText.matchAll(italicRegexes[0])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - if (!matches.length) { - matches = [...lineText.matchAll(italicRegexes[1])].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - } - } else { - matches = [...lineText.matchAll(/\`[^\`]*\`/g)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length >= cursorChar); - } - if (matches.length) { - // should only be one match, so select first and select group 1 for italics because that contains just the italic section - // doesn't include the leading and trailing characters which are guaranteed to not be * so as not to be confused with bold - const match = isItalic ? matches[0][1] : matches[0][0]; - const startIndex = lineText.indexOf(match); - const cursorOnType = cursorChar === startIndex || cursorChar === startIndex + match.length; - const contentAndType = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex, cursorLine, startIndex + match.length), parent); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, startIndex + 1, cursorLine, startIndex + match.length - 1), contentAndType); - return cursorOnType ? contentAndType : content; - } - return undefined; -} - -function createLinkRange(lineText: string, cursorChar: number, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined { - const regex = /(\[[^\(\)]*\])(\([^\[\]]*\))/g; - const matches = [...lineText.matchAll(regex)].filter(match => lineText.indexOf(match[0]) <= cursorChar && lineText.indexOf(match[0]) + match[0].length > cursorChar); - - if (matches.length) { - // should only be one match, so select first and index 0 contains the entire match, so match = [text](url) - const link = matches[0][0]; - const linkRange = new vscode.SelectionRange(new vscode.Range(cursorLine, lineText.indexOf(link), cursorLine, lineText.indexOf(link) + link.length), parent); - - const linkText = matches[0][1]; - const url = matches[0][2]; - - // determine if cursor is within [text] or (url) in order to know which should be selected - const nearestType = cursorChar >= lineText.indexOf(linkText) && cursorChar < lineText.indexOf(linkText) + linkText.length ? linkText : url; - - const indexOfType = lineText.indexOf(nearestType); - // determine if cursor is on a bracket or paren and if so, return the [content] or (content), skipping over the content range - const cursorOnType = cursorChar === indexOfType || cursorChar === indexOfType + nearestType.length; - - const contentAndNearestType = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType, cursorLine, indexOfType + nearestType.length), linkRange); - const content = new vscode.SelectionRange(new vscode.Range(cursorLine, indexOfType + 1, cursorLine, indexOfType + nearestType.length - 1), contentAndNearestType); - return cursorOnType ? contentAndNearestType : content; - } - return undefined; -} - -function isList(token: Token): boolean { - return token.type ? ['ordered_list_open', 'list_item_open', 'bullet_list_open'].includes(token.type) : false; -} - -function isBlockElement(token: Token): boolean { - return !['list_item_close', 'paragraph_close', 'bullet_list_close', 'inline', 'heading_close', 'heading_open'].includes(token.type); -} - -function getFirstChildHeader(document: SkinnyTextDocument, header?: TocEntry, toc?: readonly TocEntry[]): vscode.Position | undefined { - let childRange: vscode.Position | undefined; - if (header && toc) { - const children = toc.filter(t => header.sectionLocation.range.contains(t.sectionLocation.range) && t.sectionLocation.range.start.line > header.sectionLocation.range.start.line).sort((t1, t2) => t1.line - t2.line); - if (children.length > 0) { - childRange = children[0].sectionLocation.range.start; - const lineText = document.lineAt(childRange.line - 1).text; - return childRange ? childRange.translate(-1, lineText.length) : undefined; - } - } - return undefined; -} diff --git a/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts b/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts new file mode 100644 index 00000000000..8b3536f0c47 --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/updatePathsOnRename.ts @@ -0,0 +1,245 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as picomatch from 'picomatch'; +import * as vscode from 'vscode'; +import { BaseLanguageClient } from 'vscode-languageclient'; +import * as nls from 'vscode-nls'; +import { getEditForFileRenames } from '../protocol'; +import { Delayer } from '../util/async'; +import { noopToken } from '../util/cancellation'; +import { Disposable } from '../util/dispose'; +import { looksLikeMarkdownPath } from '../util/file'; +import { convertRange } from './fileReferences'; + +const localize = nls.loadMessageBundle(); + +const settingNames = Object.freeze({ + enabled: 'experimental.updateLinksOnFileMove.enabled', + externalFileGlobs: 'experimental.updateLinksOnFileMove.externalFileGlobs', + enableForDirectories: 'experimental.updateLinksOnFileMove.enableForDirectories', +}); + +const enum UpdateLinksOnFileMoveSetting { + Prompt = 'prompt', + Always = 'always', + Never = 'never', +} + +interface RenameAction { + readonly oldUri: vscode.Uri; + readonly newUri: vscode.Uri; +} + +class UpdateLinksOnFileRenameHandler extends Disposable { + + private readonly _delayer = new Delayer(50); + private readonly _pendingRenames = new Set(); + + public constructor( + private readonly client: BaseLanguageClient, + ) { + super(); + + this._register(vscode.workspace.onDidRenameFiles(async (e) => { + for (const { newUri, oldUri } of e.files) { + const config = this.getConfiguration(newUri); + if (!await this.shouldParticipateInLinkUpdate(config, newUri)) { + continue; + } + + this._pendingRenames.add({ newUri, oldUri }); + } + + if (this._pendingRenames.size) { + this._delayer.trigger(() => { + vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: localize('renameProgress.title', "Checking for Markdown links to update") + }, () => this.flushRenames()); + }); + } + })); + } + + private async flushRenames(): Promise { + const renames = Array.from(this._pendingRenames); + this._pendingRenames.clear(); + + const edit = new vscode.WorkspaceEdit(); + const resourcesBeingRenamed: vscode.Uri[] = []; + + for (const { oldUri, newUri } of renames) { + if (await this.withEditsForFileRename(edit, oldUri, newUri, noopToken)) { + resourcesBeingRenamed.push(newUri); + } + } + + if (edit.size) { + if (await this.confirmActionWithUser(resourcesBeingRenamed)) { + await vscode.workspace.applyEdit(edit); + } + } + } + + private async confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise { + if (!newResources.length) { + return false; + } + + const config = this.getConfiguration(newResources[0]); + const setting = config.get(settingNames.enabled); + switch (setting) { + case UpdateLinksOnFileMoveSetting.Prompt: + return this.promptUser(newResources); + case UpdateLinksOnFileMoveSetting.Always: + return true; + case UpdateLinksOnFileMoveSetting.Never: + default: + return false; + } + } + + private getConfiguration(resource: vscode.Uri) { + return vscode.workspace.getConfiguration('markdown', resource); + } + + private async shouldParticipateInLinkUpdate(config: vscode.WorkspaceConfiguration, newUri: vscode.Uri): Promise { + const setting = config.get(settingNames.enabled); + if (setting === UpdateLinksOnFileMoveSetting.Never) { + return false; + } + + if (looksLikeMarkdownPath(newUri)) { + return true; + } + + const externalGlob = config.get(settingNames.externalFileGlobs); + if (!!externalGlob && picomatch.isMatch(newUri.fsPath, externalGlob)) { + return true; + } + + const stat = await vscode.workspace.fs.stat(newUri); + if (stat.type === vscode.FileType.Directory) { + return config.get(settingNames.enableForDirectories, true); + } + + return false; + } + + private async promptUser(newResources: readonly vscode.Uri[]): Promise { + if (!newResources.length) { + return false; + } + + const enum Choice { + None = 0, + Accept = 1, + Reject = 2, + Always = 3, + Never = 4, + } + + interface Item extends vscode.MessageItem { + readonly choice: Choice; + } + + const response = await vscode.window.showInformationMessage( + newResources.length === 1 + ? localize('prompt', "Update Markdown links for '{0}'?", path.basename(newResources[0].fsPath)) + : this.getConfirmMessage(localize('promptMoreThanOne', "Update Markdown link for the following {0} files?", newResources.length), newResources), { + modal: true, + }, { + title: localize('reject.title', "No"), + choice: Choice.Reject, + isCloseAffordance: true, + }, { + title: localize('accept.title', "Yes"), + choice: Choice.Accept, + }, { + title: localize('always.title', "Always automatically update Markdown Links"), + choice: Choice.Always, + }, { + title: localize('never.title', "Never automatically update Markdown Links"), + choice: Choice.Never, + }); + + if (!response) { + return false; + } + + switch (response.choice) { + case Choice.Accept: { + return true; + } + case Choice.Reject: { + return false; + } + case Choice.Always: { + const config = this.getConfiguration(newResources[0]); + config.update( + settingNames.enabled, + UpdateLinksOnFileMoveSetting.Always, + vscode.ConfigurationTarget.Global); + return true; + } + case Choice.Never: { + const config = this.getConfiguration(newResources[0]); + config.update( + settingNames.enabled, + UpdateLinksOnFileMoveSetting.Never, + vscode.ConfigurationTarget.Global); + return false; + } + } + + return false; + } + + private async withEditsForFileRename( + workspaceEdit: vscode.WorkspaceEdit, + oldUri: vscode.Uri, + newUri: vscode.Uri, + token: vscode.CancellationToken, + ): Promise { + const edit = await this.client.sendRequest(getEditForFileRenames, [{ oldUri: oldUri.toString(), newUri: newUri.toString() }], token); + if (!edit.changes) { + return false; + } + + for (const [path, edits] of Object.entries(edit.changes)) { + const uri = vscode.Uri.parse(path); + for (const edit of edits) { + workspaceEdit.replace(uri, convertRange(edit.range), edit.newText); + } + } + + return true; + } + + private getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string { + const MAX_CONFIRM_FILES = 10; + + const paths = [start]; + paths.push(''); + paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath))); + + if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { + if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { + paths.push(localize('moreFile', "...1 additional file not shown")); + } else { + paths.push(localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES)); + } + } + + paths.push(''); + return paths.join('\n'); + } +} + +export function registerUpdateLinksOnRename(client: BaseLanguageClient) { + return new UpdateLinksOnFileRenameHandler(client); +} diff --git a/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts b/extensions/markdown-language-features/src/languageFeatures/workspaceCache.ts deleted file mode 100644 index 295771bfa60..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/workspaceCache.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 vscode from 'vscode'; -import { Disposable } from '../util/dispose'; -import { Lazy, lazy } from '../util/lazy'; -import { ResourceMap } from '../util/resourceMap'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; - -/** - * Cache of information for markdown files in the workspace. - */ -export class MdWorkspaceCache extends Disposable { - - private readonly _cache = new ResourceMap>>(); - private _init?: Promise; - - public constructor( - private readonly workspaceContents: MdWorkspaceContents, - private readonly getValue: (document: SkinnyTextDocument) => Promise, - ) { - super(); - } - - public async entries(): Promise> { - await this.ensureInit(); - return Promise.all(Array.from(this._cache.entries(), async ([key, entry]) => { - return [key, await entry.value]; - })); - } - - public async values(): Promise> { - await this.ensureInit(); - return Promise.all(Array.from(this._cache.values(), x => x.value)); - } - - private async ensureInit(): Promise { - if (!this._init) { - this._init = this.populateCache(); - - this._register(this.workspaceContents.onDidChangeMarkdownDocument(this.onDidChangeDocument, this)); - this._register(this.workspaceContents.onDidCreateMarkdownDocument(this.onDidChangeDocument, this)); - this._register(this.workspaceContents.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this)); - } - await this._init; - } - - private async populateCache(): Promise { - const markdownDocumentUris = await this.workspaceContents.getAllMarkdownDocuments(); - for (const document of markdownDocumentUris) { - this.update(document); - } - } - - private update(document: SkinnyTextDocument): void { - this._cache.set(document.uri, lazy(() => this.getValue(document))); - } - - private onDidChangeDocument(document: SkinnyTextDocument) { - this.update(document); - } - - private onDidDeleteDocument(resource: vscode.Uri) { - this._cache.delete(resource); - } -} diff --git a/extensions/markdown-language-features/src/languageFeatures/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/languageFeatures/workspaceSymbolProvider.ts deleted file mode 100644 index 5906cc1cc72..00000000000 --- a/extensions/markdown-language-features/src/languageFeatures/workspaceSymbolProvider.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 * as vscode from 'vscode'; -import { Disposable } from '../util/dispose'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { MdDocumentSymbolProvider } from './documentSymbolProvider'; -import { MdWorkspaceCache } from './workspaceCache'; - -export class MdWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider { - - private readonly _cache: MdWorkspaceCache; - - public constructor( - symbolProvider: MdDocumentSymbolProvider, - workspaceContents: MdWorkspaceContents, - ) { - super(); - - this._cache = this._register(new MdWorkspaceCache(workspaceContents, doc => symbolProvider.provideDocumentSymbolInformation(doc))); - } - - public async provideWorkspaceSymbols(query: string): Promise { - const allSymbols = (await this._cache.values()).flat(); - return allSymbols.filter(symbolInformation => symbolInformation.name.toLowerCase().indexOf(query.toLowerCase()) !== -1); - } -} diff --git a/extensions/markdown-language-features/src/logger.ts b/extensions/markdown-language-features/src/logging.ts similarity index 59% rename from extensions/markdown-language-features/src/logger.ts rename to extensions/markdown-language-features/src/logging.ts index 6f4d87b7a4a..5947af62409 100644 --- a/extensions/markdown-language-features/src/logger.ts +++ b/extensions/markdown-language-features/src/logging.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { Disposable } from './util/dispose'; import { lazy } from './util/lazy'; enum Trace { @@ -25,57 +26,61 @@ namespace Trace { } } - -function isString(value: any): value is string { - return Object.prototype.toString.call(value) === '[object String]'; +export interface ILogger { + verbose(title: string, message: string, data?: any): void; } -export class Logger { +export class VsCodeOutputLogger extends Disposable implements ILogger { private trace?: Trace; - private readonly outputChannel = lazy(() => vscode.window.createOutputChannel('Markdown')); + private readonly outputChannel = lazy(() => this._register(vscode.window.createOutputChannel('Markdown'))); constructor() { + super(); + + this._register(vscode.workspace.onDidChangeConfiguration(() => { + this.updateConfiguration(); + })); + this.updateConfiguration(); } - public log(message: string, data?: any): void { + public verbose(title: string, message: string, data?: any): void { if (this.trace === Trace.Verbose) { - this.appendLine(`[Log - ${this.now()}] ${message}`); + this.appendLine(`[Verbose ${this.now()}] ${title}: ${message}`); if (data) { - this.appendLine(Logger.data2String(data)); + this.appendLine(VsCodeOutputLogger.data2String(data)); } } } - private now(): string { const now = new Date(); return String(now.getUTCHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') - + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + now.getMilliseconds(); + + ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0'); } - public updateConfiguration() { + private updateConfiguration(): void { this.trace = this.readTrace(); } - private appendLine(value: string) { - return this.outputChannel.value.appendLine(value); + private appendLine(value: string): void { + this.outputChannel.value.appendLine(value); } private readTrace(): Trace { - return Trace.fromString(vscode.workspace.getConfiguration().get('markdown.trace', 'off')); + return Trace.fromString(vscode.workspace.getConfiguration().get('markdown.trace.extension', 'off')); } private static data2String(data: any): string { if (data instanceof Error) { - if (isString(data.stack)) { + if (typeof data.stack === 'string') { return data.stack; } - return (data as Error).message; + return data.message; } - if (isString(data)) { + if (typeof data === 'string') { return data; } return JSON.stringify(data, undefined, 2); diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 3f32880d22f..9f7142fc91a 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -3,15 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import MarkdownIt = require('markdown-it'); -import Token = require('markdown-it/lib/token'); +import type MarkdownIt = require('markdown-it'); +import type Token = require('markdown-it/lib/token'); import * as vscode from 'vscode'; +import { ILogger } from './logging'; import { MarkdownContributionProvider } from './markdownExtensions'; import { Slugifier } from './slugify'; -import { stringHash } from './util/hash'; +import { ITextDocument } from './types/textDocument'; +import { Disposable } from './util/dispose'; import { WebviewResourceProvider } from './util/resources'; import { isOfScheme, Schemes } from './util/schemes'; -import { SkinnyTextDocument } from './workspaceContents'; +import { MdDocumentInfoCache } from './util/workspaceCache'; +import { IMdWorkspace } from './workspace'; const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g; @@ -53,7 +56,7 @@ class TokenCache { }; private tokens?: Token[]; - public tryGetCached(document: SkinnyTextDocument, config: MarkdownItConfig): Token[] | undefined { + public tryGetCached(document: ITextDocument, config: MarkdownItConfig): Token[] | undefined { if (this.cachedDocument && this.cachedDocument.uri.toString() === document.uri.toString() && this.cachedDocument.version === document.version @@ -65,7 +68,7 @@ class TokenCache { return undefined; } - public update(document: SkinnyTextDocument, config: MarkdownItConfig, tokens: Token[]) { + public update(document: ITextDocument, config: MarkdownItConfig, tokens: Token[]) { this.cachedDocument = { uri: document.uri, version: document.version, @@ -82,26 +85,37 @@ class TokenCache { export interface RenderOutput { html: string; - containingImages: { src: string }[]; + containingImages: Set; } interface RenderEnv { - containingImages: { src: string }[]; + containingImages: Set; currentDocument: vscode.Uri | undefined; resourceProvider: WebviewResourceProvider | undefined; } -export class MarkdownEngine { +export interface IMdParser { + readonly slugifier: Slugifier; + + tokenize(document: ITextDocument): Promise; +} + +export class MarkdownItEngine implements IMdParser { private md?: Promise; private _slugCount = new Map(); private _tokenCache = new TokenCache(); + public readonly slugifier: Slugifier; + public constructor( private readonly contributionProvider: MarkdownContributionProvider, - private readonly slugifier: Slugifier, + slugifier: Slugifier, + private readonly logger: ILogger, ) { + this.slugifier = slugifier; + contributionProvider.onContributionsChanged(() => { // Markdown plugin contributions may have changed this.md = undefined; @@ -159,7 +173,7 @@ export class MarkdownEngine { } private tokenizeDocument( - document: SkinnyTextDocument, + document: ITextDocument, config: MarkdownItConfig, engine: MarkdownIt ): Token[] { @@ -169,6 +183,7 @@ export class MarkdownEngine { return cached; } + this.logger.verbose('MarkdownItEngine', `tokenizeDocument - ${document.uri}`); const tokens = this.tokenizeString(document.getText(), engine); this._tokenCache.update(document, config, tokens); return tokens; @@ -184,7 +199,7 @@ export class MarkdownEngine { this._slugCount = new Map(); } - public async render(input: SkinnyTextDocument | string, resourceProvider?: WebviewResourceProvider): Promise { + public async render(input: ITextDocument | string, resourceProvider?: WebviewResourceProvider): Promise { const config = this.getConfig(typeof input === 'string' ? undefined : input.uri); const engine = await this.getEngine(config); @@ -193,7 +208,7 @@ export class MarkdownEngine { : this.tokenizeDocument(input, config, engine); const env: RenderEnv = { - containingImages: [], + containingImages: new Set(), currentDocument: typeof input === 'string' ? undefined : input.uri, resourceProvider, }; @@ -209,7 +224,7 @@ export class MarkdownEngine { }; } - public async parse(document: SkinnyTextDocument): Promise { + public async tokenize(document: ITextDocument): Promise { const config = this.getConfig(document.uri); const engine = await this.getEngine(config); return this.tokenizeDocument(document, config, engine); @@ -232,13 +247,9 @@ export class MarkdownEngine { const original = md.renderer.rules.image; md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => { const token = tokens[idx]; - token.attrJoin('class', 'loading'); - const src = token.attrGet('src'); if (src) { - env.containingImages?.push({ src }); - const imgHash = stringHash(src); - token.attrSet('id', `image-hash-${imgHash}`); + env.containingImages?.add(src); if (!token.attrGet('data-src')) { token.attrSet('src', this.toResourceUri(src, env.currentDocument, env.resourceProvider)); @@ -423,3 +434,27 @@ function normalizeHighlightLang(lang: string | undefined) { return lang; } } + +export class MdParsingProvider extends Disposable implements IMdParser { + + private readonly _cache: MdDocumentInfoCache; + + public readonly slugifier: Slugifier; + + constructor( + engine: MarkdownItEngine, + workspace: IMdWorkspace, + ) { + super(); + + this.slugifier = engine.slugifier; + + this._cache = this._register(new MdDocumentInfoCache(workspace, doc => { + return engine.tokenize(doc); + })); + } + + public tokenize(document: ITextDocument): Promise { + return this._cache.getForDocument(document); + } +} diff --git a/extensions/markdown-language-features/src/preview/previewContentProvider.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts similarity index 91% rename from extensions/markdown-language-features/src/preview/previewContentProvider.ts rename to extensions/markdown-language-features/src/preview/documentRenderer.ts index 8437017dc4f..8acbb9db2a9 100644 --- a/extensions/markdown-language-features/src/preview/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -6,9 +6,10 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as uri from 'vscode-uri'; -import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; +import { ILogger } from '../logging'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { escapeAttribute, getNonce } from '../util/dom'; import { WebviewResourceProvider } from '../util/resources'; import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig'; import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from './security'; @@ -35,23 +36,19 @@ const previewStrings = { 'Content Disabled Security Warning') }; -function escapeAttribute(value: string | vscode.Uri): string { - return value.toString().replace(/"/g, '"'); -} - export interface MarkdownContentProviderOutput { html: string; - containingImages: { src: string }[]; + containingImages: Set; } -export class MarkdownContentProvider { +export class MdDocumentRenderer { constructor( - private readonly engine: MarkdownEngine, + private readonly engine: MarkdownItEngine, private readonly context: vscode.ExtensionContext, private readonly cspArbiter: ContentSecurityPolicyArbiter, private readonly contributionProvider: MarkdownContributionProvider, - private readonly logger: Logger + private readonly logger: ILogger ) { this.iconPath = { dark: vscode.Uri.joinPath(this.context.extensionUri, 'media', 'preview-dark.svg'), @@ -61,7 +58,7 @@ export class MarkdownContentProvider { public readonly iconPath: { light: vscode.Uri; dark: vscode.Uri }; - public async provideTextDocumentContent( + public async renderDocument( markdownDocument: vscode.TextDocument, resourceProvider: WebviewResourceProvider, previewConfigurations: MarkdownPreviewConfigurationManager, @@ -83,15 +80,15 @@ export class MarkdownContentProvider { webviewResourceRoot: resourceProvider.asWebviewUri(markdownDocument.uri).toString(), }; - this.logger.log('provideTextDocumentContent', initialData); + this.logger.verbose('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData); // Content Security Policy const nonce = getNonce(); const csp = this.getCsp(resourceProvider, sourceUri, nonce); - const body = await this.markdownBody(markdownDocument, resourceProvider); + const body = await this.renderBody(markdownDocument, resourceProvider); if (token.isCancellationRequested) { - return { html: '', containingImages: [] }; + return { html: '', containingImages: new Set() }; } const html = ` @@ -118,7 +115,7 @@ export class MarkdownContentProvider { }; } - public async markdownBody( + public async renderBody( markdownDocument: vscode.TextDocument, resourceProvider: WebviewResourceProvider, ): Promise { @@ -130,9 +127,7 @@ export class MarkdownContentProvider { }; } - public provideFileNotFoundContent( - resource: vscode.Uri, - ): string { + public renderFileNotFoundDocument(resource: vscode.Uri): string { const resourcePath = uri.Utils.basename(resource); const body = localize('preview.notFound', '{0} cannot be found', resourcePath); return ` @@ -251,12 +246,3 @@ export class MarkdownContentProvider { } } } - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index dd3c310a194..6ce2b644f1c 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -6,16 +6,17 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import * as uri from 'vscode-uri'; -import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; +import { ILogger } from '../logging'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { openDocumentLink, resolveDocumentLink, resolveUriToMarkdownFile } from '../util/openDocumentLink'; import { WebviewResourceProvider } from '../util/resources'; import { urlToUri } from '../util/url'; +import { IMdWorkspace } from '../workspace'; +import { MdDocumentRenderer } from './documentRenderer'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; -import { MarkdownContentProvider } from './previewContentProvider'; import { scrollEditorToLine, StartingScrollFragment, StartingScrollLine, StartingScrollLocation } from './scrolling'; import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from './topmostLineMonitor'; @@ -116,11 +117,12 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { resource: vscode.Uri, startingScroll: StartingScrollLocation | undefined, private readonly delegate: MarkdownPreviewDelegate, - private readonly engine: MarkdownEngine, - private readonly _contentProvider: MarkdownContentProvider, + private readonly _contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, - private readonly _logger: Logger, + private readonly _workspace: IMdWorkspace, + private readonly _logger: ILogger, private readonly _contributionProvider: MarkdownContributionProvider, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -269,7 +271,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { return; } - this._logger.log('updateForView', { markdownFile: this._resource }); + this._logger.verbose('MarkdownPreview', 'updateForView', { markdownFile: this._resource }); this.line = topLine; this.postMessage({ type: 'updateView', @@ -312,8 +314,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { this.currentVersion = pendingVersion; const content = await (shouldReloadPage - ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state, this._disposeCts.token) - : this._contentProvider.markdownBody(document, this)); + ? this._contentProvider.renderDocument(document, this, this._previewConfigurations, this.line, this.state, this._disposeCts.token) + : this._contentProvider.renderBody(document, this)); // Another call to `doUpdate` may have happened. // Make sure we are still updating for the correct document @@ -370,7 +372,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } private async showFileNotFoundError() { - this._webviewPanel.webview.html = this._contentProvider.provideFileNotFoundContent(this._resource); + this._webviewPanel.webview.html = this._contentProvider.renderFileNotFoundDocument(this._resource); } private updateWebviewContent(html: string, reloadPage: boolean): void { @@ -394,9 +396,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } } - private updateImageWatchers(containingImages: { src: string }[]) { - const srcs = new Set(containingImages.map(img => img.src)); - + private updateImageWatchers(srcs: Set) { // Delete stale file watchers. for (const [src, watcher] of this._fileWatchersBySrc) { if (!srcs.has(src)) { @@ -449,14 +449,14 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { const config = vscode.workspace.getConfiguration('markdown', this.resource); const openLinks = config.get('preview.openMarkdownLinks', 'inPreview'); if (openLinks === 'inPreview') { - const linkedDoc = await resolveUriToMarkdownFile(targetResource); + const linkedDoc = await resolveUriToMarkdownFile(this._workspace, targetResource); if (linkedDoc) { this.delegate.openPreviewLinkToMarkdownFile(linkedDoc.uri, targetResource.fragment); return; } } - return openDocumentLink(this.engine, targetResource, this.resource); + return openDocumentLink(this._tocProvider, targetResource, this.resource); } //#region WebviewResourceProvider @@ -472,7 +472,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { //#endregion } -export interface ManagedMarkdownPreview { +export interface IManagedMarkdownPreview { readonly resource: vscode.Uri; readonly resourceColumn: vscode.ViewColumn; @@ -492,22 +492,23 @@ export interface ManagedMarkdownPreview { ): boolean; } -export class StaticMarkdownPreview extends Disposable implements ManagedMarkdownPreview { +export class StaticMarkdownPreview extends Disposable implements IManagedMarkdownPreview { public static readonly customEditorViewType = 'vscode.markdown.preview.editor'; public static revive( resource: vscode.Uri, webview: vscode.WebviewPanel, - contentProvider: MarkdownContentProvider, + contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, topmostLineMonitor: TopmostLineMonitor, - logger: Logger, + workspace: IMdWorkspace, + logger: ILogger, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, scrollLine?: number, ): StaticMarkdownPreview { - return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, engine, scrollLine); + return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, workspace, logger, contributionProvider, tocProvider, scrollLine); } private readonly preview: MarkdownPreview; @@ -515,12 +516,13 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown private constructor( private readonly _webviewPanel: vscode.WebviewPanel, resource: vscode.Uri, - contentProvider: MarkdownContentProvider, + contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, topmostLineMonitor: TopmostLineMonitor, - logger: Logger, + workspace: IMdWorkspace, + logger: ILogger, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, scrollLine?: number, ) { super(); @@ -532,7 +534,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown fragment }), StaticMarkdownPreview.customEditorViewType, this._webviewPanel.viewColumn); } - }, engine, contentProvider, _previewConfigurations, logger, contributionProvider)); + }, contentProvider, _previewConfigurations, workspace, logger, contributionProvider, tocProvider)); this._register(this._webviewPanel.onDidDispose(() => { this.dispose(); @@ -598,7 +600,7 @@ interface DynamicPreviewInput { readonly line?: number; } -export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdownPreview { +export class DynamicMarkdownPreview extends Disposable implements IManagedMarkdownPreview { public static readonly viewType = 'markdown.preview'; @@ -611,28 +613,30 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow public static revive( input: DynamicPreviewInput, webview: vscode.WebviewPanel, - contentProvider: MarkdownContentProvider, + contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, - logger: Logger, + workspace: IMdWorkspace, + logger: ILogger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, ): DynamicMarkdownPreview { webview.iconPath = contentProvider.iconPath; return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); + contentProvider, previewConfigurations, workspace, logger, topmostLineMonitor, contributionProvider, tocProvider); } public static create( input: DynamicPreviewInput, previewColumn: vscode.ViewColumn, - contentProvider: MarkdownContentProvider, + contentProvider: MdDocumentRenderer, previewConfigurations: MarkdownPreviewConfigurationManager, - logger: Logger, + workspace: IMdWorkspace, + logger: ILogger, topmostLineMonitor: TopmostLineMonitor, contributionProvider: MarkdownContributionProvider, - engine: MarkdownEngine, + tocProvider: MdTableOfContentsProvider, ): DynamicMarkdownPreview { const webview = vscode.window.createWebviewPanel( DynamicMarkdownPreview.viewType, @@ -642,18 +646,19 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow webview.iconPath = contentProvider.iconPath; return new DynamicMarkdownPreview(webview, input, - contentProvider, previewConfigurations, logger, topmostLineMonitor, contributionProvider, engine); + contentProvider, previewConfigurations, workspace, logger, topmostLineMonitor, contributionProvider, tocProvider); } private constructor( webview: vscode.WebviewPanel, input: DynamicPreviewInput, - private readonly _contentProvider: MarkdownContentProvider, + private readonly _contentProvider: MdDocumentRenderer, private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, - private readonly _logger: Logger, + private readonly _workspace: IMdWorkspace, + private readonly _logger: ILogger, private readonly _topmostLineMonitor: TopmostLineMonitor, private readonly _contributionProvider: MarkdownContributionProvider, - private readonly _engine: MarkdownEngine, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -805,10 +810,11 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow this.update(link, fragment ? new StartingScrollFragment(fragment) : undefined); } }, - this._engine, this._contentProvider, this._previewConfigurations, + this._workspace, this._logger, - this._contributionProvider); + this._contributionProvider, + this._tocProvider); } } diff --git a/extensions/markdown-language-features/src/preview/previewManager.ts b/extensions/markdown-language-features/src/preview/previewManager.ts index a0f837c2425..ca20542577c 100644 --- a/extensions/markdown-language-features/src/preview/previewManager.ts +++ b/extensions/markdown-language-features/src/preview/previewManager.ts @@ -4,16 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Logger } from '../logger'; -import { MarkdownEngine } from '../markdownEngine'; +import { ILogger } from '../logging'; import { MarkdownContributionProvider } from '../markdownExtensions'; +import { MdTableOfContentsProvider } from '../tableOfContents'; import { Disposable, disposeAll } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; -import { DynamicMarkdownPreview, ManagedMarkdownPreview, StaticMarkdownPreview } from './preview'; +import { IMdWorkspace } from '../workspace'; +import { MdDocumentRenderer } from './documentRenderer'; +import { DynamicMarkdownPreview, IManagedMarkdownPreview, StaticMarkdownPreview } from './preview'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; -import { MarkdownContentProvider } from './previewContentProvider'; import { scrollEditorToLine, StartingScrollFragment } from './scrolling'; import { TopmostLineMonitor } from './topmostLineMonitor'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + export interface DynamicPreviewSettings { readonly resourceColumn: vscode.ViewColumn; @@ -21,7 +26,7 @@ export interface DynamicPreviewSettings { readonly locked: boolean; } -class PreviewStore extends Disposable { +class PreviewStore extends Disposable { private readonly _previews = new Set(); @@ -57,21 +62,20 @@ class PreviewStore extends Disposable { export class MarkdownPreviewManager extends Disposable implements vscode.WebviewPanelSerializer, vscode.CustomTextEditorProvider { - private static readonly markdownPreviewActiveContextKey = 'markdownPreviewFocus'; - private readonly _topmostLineMonitor = new TopmostLineMonitor(); private readonly _previewConfigurations = new MarkdownPreviewConfigurationManager(); private readonly _dynamicPreviews = this._register(new PreviewStore()); private readonly _staticPreviews = this._register(new PreviewStore()); - private _activePreview: ManagedMarkdownPreview | undefined = undefined; + private _activePreview: IManagedMarkdownPreview | undefined = undefined; public constructor( - private readonly _contentProvider: MarkdownContentProvider, - private readonly _logger: Logger, + private readonly _contentProvider: MdDocumentRenderer, + private readonly _workspace: IMdWorkspace, + private readonly _logger: ILogger, private readonly _contributions: MarkdownContributionProvider, - private readonly _engine: MarkdownEngine, + private readonly _tocProvider: MdTableOfContentsProvider, ) { super(); @@ -153,22 +157,59 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview webview: vscode.WebviewPanel, state: any ): Promise { - const resource = vscode.Uri.parse(state.resource); - const locked = state.locked; - const line = state.line; - const resourceColumn = state.resourceColumn; + try { + const resource = vscode.Uri.parse(state.resource); + const locked = state.locked; + const line = state.line; + const resourceColumn = state.resourceColumn; - const preview = await DynamicMarkdownPreview.revive( - { resource, locked, line, resourceColumn }, - webview, - this._contentProvider, - this._previewConfigurations, - this._logger, - this._topmostLineMonitor, - this._contributions, - this._engine); + const preview = DynamicMarkdownPreview.revive( + { resource, locked, line, resourceColumn }, + webview, + this._contentProvider, + this._previewConfigurations, + this._workspace, + this._logger, + this._topmostLineMonitor, + this._contributions, + this._tocProvider); - this.registerDynamicPreview(preview); + this.registerDynamicPreview(preview); + } catch (e) { + console.error(e); + + webview.webview.html = /* html */` + + + + + + + + Markdown Preview + + + + + + +

${localize('preview.restoreError', "An unexpected error occurred while restoring the Markdown preview.")}

+ + `; + } } public async resolveCustomTextEditor( @@ -182,9 +223,10 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this._contentProvider, this._previewConfigurations, this._topmostLineMonitor, + this._workspace, this._logger, this._contributions, - this._engine, + this._tocProvider, lineNumber ); this.registerStaticPreview(preview); @@ -206,12 +248,12 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview previewSettings.previewColumn, this._contentProvider, this._previewConfigurations, + this._workspace, this._logger, this._topmostLineMonitor, this._contributions, - this._engine); + this._tocProvider); - this.setPreviewActiveContext(true); this._activePreview = preview; return this.registerDynamicPreview(preview); } @@ -243,21 +285,16 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview return preview; } - private trackActive(preview: ManagedMarkdownPreview): void { + private trackActive(preview: IManagedMarkdownPreview): void { preview.onDidChangeViewState(({ webviewPanel }) => { - this.setPreviewActiveContext(webviewPanel.active); this._activePreview = webviewPanel.active ? preview : undefined; }); preview.onDispose(() => { if (this._activePreview === preview) { - this.setPreviewActiveContext(false); this._activePreview = undefined; } }); } - private setPreviewActiveContext(value: boolean) { - vscode.commands.executeCommand('setContext', MarkdownPreviewManager.markdownPreviewActiveContextKey, value); - } } diff --git a/extensions/markdown-language-features/src/protocol.ts b/extensions/markdown-language-features/src/protocol.ts new file mode 100644 index 00000000000..61a13a8bd88 --- /dev/null +++ b/extensions/markdown-language-features/src/protocol.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type Token = require('markdown-it/lib/token'); +import { RequestType } from 'vscode-languageclient'; +import type * as lsp from 'vscode-languageserver-types'; +import type * as md from 'vscode-markdown-languageservice'; + +//#region From server +export const parse = new RequestType<{ uri: string }, Token[], any>('markdown/parse'); + +export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile'); +export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory'); +export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat'); + +export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions; watchParentDirs: boolean }, void, any>('markdown/fs/watcher/create'); +export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete'); + +export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace'); +//#endregion + +//#region To server +export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace'); +export const getEditForFileRenames = new RequestType, lsp.WorkspaceEdit, any>('markdown/getEditForFileRenames'); + +export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange'); +//#endregion diff --git a/extensions/markdown-language-features/src/tableOfContents.ts b/extensions/markdown-language-features/src/tableOfContents.ts index 33a65d997b5..5e0a0cc3684 100644 --- a/extensions/markdown-language-features/src/tableOfContents.ts +++ b/extensions/markdown-language-features/src/tableOfContents.ts @@ -4,10 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { MarkdownEngine } from './markdownEngine'; -import { githubSlugifier, Slug } from './slugify'; +import { ILogger } from './logging'; +import { IMdParser } from './markdownEngine'; +import { githubSlugifier, Slug, Slugifier } from './slugify'; +import { getLine, ITextDocument } from './types/textDocument'; +import { Disposable } from './util/dispose'; import { isMarkdownFile } from './util/file'; -import { SkinnyTextDocument } from './workspaceContents'; +import { Schemes } from './util/schemes'; +import { MdDocumentInfoCache } from './util/workspaceCache'; +import { IMdWorkspace } from './workspace'; export interface TocEntry { readonly slug: Slug; @@ -61,35 +66,39 @@ export interface TocEntry { export class TableOfContents { - public static async create(engine: MarkdownEngine, document: SkinnyTextDocument,): Promise { - const entries = await this.buildToc(engine, document); - return new TableOfContents(entries); + public static async create(parser: IMdParser, document: ITextDocument,): Promise { + const entries = await this.buildToc(parser, document); + return new TableOfContents(entries, parser.slugifier); } - public static async createForDocumentOrNotebook(engine: MarkdownEngine, document: SkinnyTextDocument): Promise { - if (document.uri.scheme === 'vscode-notebook-cell') { + public static async createForDocumentOrNotebook(parser: IMdParser, document: ITextDocument): Promise { + if (document.uri.scheme === Schemes.notebookCell) { const notebook = vscode.workspace.notebookDocuments .find(notebook => notebook.getCells().some(cell => cell.document === document)); if (notebook) { - const entries: TocEntry[] = []; - - for (const cell of notebook.getCells()) { - if (cell.kind === vscode.NotebookCellKind.Markup && isMarkdownFile(cell.document)) { - entries.push(...(await this.buildToc(engine, cell.document))); - } - } - - return new TableOfContents(entries); + return TableOfContents.createForNotebook(parser, notebook); } } - return this.create(engine, document); + return this.create(parser, document); } - private static async buildToc(engine: MarkdownEngine, document: SkinnyTextDocument): Promise { + public static async createForNotebook(parser: IMdParser, notebook: vscode.NotebookDocument): Promise { + const entries: TocEntry[] = []; + + for (const cell of notebook.getCells()) { + if (cell.kind === vscode.NotebookCellKind.Markup && isMarkdownFile(cell.document)) { + entries.push(...(await this.buildToc(parser, cell.document))); + } + } + + return new TableOfContents(entries, parser.slugifier); + } + + private static async buildToc(parser: IMdParser, document: ITextDocument): Promise { const toc: TocEntry[] = []; - const tokens = await engine.parse(document); + const tokens = await parser.tokenize(document); const existingSlugEntries = new Map(); @@ -99,26 +108,26 @@ export class TableOfContents { } const lineNumber = heading.map[0]; - const line = document.lineAt(lineNumber); + const line = getLine(document, lineNumber); - let slug = githubSlugifier.fromHeading(line.text); + let slug = parser.slugifier.fromHeading(line); const existingSlugEntry = existingSlugEntries.get(slug.value); if (existingSlugEntry) { ++existingSlugEntry.count; - slug = githubSlugifier.fromHeading(slug.value + '-' + existingSlugEntry.count); + slug = parser.slugifier.fromHeading(slug.value + '-' + existingSlugEntry.count); } else { existingSlugEntries.set(slug.value, { count: 0 }); } const headerLocation = new vscode.Location(document.uri, - new vscode.Range(lineNumber, 0, lineNumber, line.text.length)); + new vscode.Range(lineNumber, 0, lineNumber, line.length)); const headerTextLocation = new vscode.Location(document.uri, - new vscode.Range(lineNumber, line.text.match(/^#+\s*/)?.[0].length ?? 0, lineNumber, line.text.length - (line.text.match(/\s*#*$/)?.[0].length ?? 0))); + new vscode.Range(lineNumber, line.match(/^#+\s*/)?.[0].length ?? 0, lineNumber, line.length - (line.match(/\s*#*$/)?.[0].length ?? 0))); toc.push({ slug, - text: TableOfContents.getHeaderText(line.text), + text: TableOfContents.getHeaderText(line), level: TableOfContents.getHeaderLevel(heading.markup), line: lineNumber, sectionLocation: headerLocation, // Populated in next steps @@ -142,7 +151,7 @@ export class TableOfContents { sectionLocation: new vscode.Location(document.uri, new vscode.Range( entry.sectionLocation.range.start, - new vscode.Position(endLine, document.lineAt(endLine).text.length))) + new vscode.Position(endLine, getLine(document, endLine).length))) }; }); } @@ -161,12 +170,44 @@ export class TableOfContents { return header.replace(/^\s*#+\s*(.*?)(\s+#+)?$/, (_, word) => word.trim()); } + public static readonly empty = new TableOfContents([], githubSlugifier); + private constructor( public readonly entries: readonly TocEntry[], + private readonly slugifier: Slugifier, ) { } public lookup(fragment: string): TocEntry | undefined { - const slug = githubSlugifier.fromHeading(fragment); + const slug = this.slugifier.fromHeading(fragment); return this.entries.find(entry => entry.slug.equals(slug)); } } + +export class MdTableOfContentsProvider extends Disposable { + + private readonly _cache: MdDocumentInfoCache; + + constructor( + private readonly parser: IMdParser, + workspace: IMdWorkspace, + private readonly logger: ILogger, + ) { + super(); + this._cache = this._register(new MdDocumentInfoCache(workspace, doc => { + this.logger.verbose('TableOfContentsProvider', `create - ${doc.uri}`); + return TableOfContents.create(parser, doc); + })); + } + + public async get(resource: vscode.Uri): Promise { + return await this._cache.get(resource) ?? TableOfContents.empty; + } + + public getForDocument(doc: ITextDocument): Promise { + return this._cache.getForDocument(doc); + } + + public createForNotebook(notebook: vscode.NotebookDocument): Promise { + return TableOfContents.createForNotebook(this.parser, notebook); + } +} diff --git a/extensions/markdown-language-features/src/test/definitionProvider.test.ts b/extensions/markdown-language-features/src/test/definitionProvider.test.ts deleted file mode 100644 index 23379965264..00000000000 --- a/extensions/markdown-language-features/src/test/definitionProvider.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdDefinitionProvider } from '../languageFeatures/definitionProvider'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { MdReferencesProvider } from '../languageFeatures/references'; -import { githubSlugifier } from '../slugify'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; -import { joinLines, workspacePath } from './util'; - - -function getDefinition(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - const provider = new MdDefinitionProvider(referencesProvider); - return provider.provideDefinition(doc, pos, noopToken); -} - -function assertDefinitionsEqual(actualDef: vscode.Definition, ...expectedDefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) { - const actualDefsArr = Array.isArray(actualDef) ? actualDef : [actualDef]; - - assert.strictEqual(actualDefsArr.length, expectedDefs.length, `Definition counts should match`); - - for (let i = 0; i < actualDefsArr.length; ++i) { - const actual = actualDefsArr[i]; - const expected = expectedDefs[i]; - assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Definition '${i}' has expected document`); - assert.strictEqual(actual.range.start.line, expected.line, `Definition '${i}' has expected start line`); - assert.strictEqual(actual.range.end.line, expected.line, `Definition '${i}' has expected end line`); - if (typeof expected.startCharacter !== 'undefined') { - assert.strictEqual(actual.range.start.character, expected.startCharacter, `Definition '${i}' has expected start character`); - } - if (typeof expected.endCharacter !== 'undefined') { - assert.strictEqual(actual.range.end.character, expected.endCharacter, `Definition '${i}' has expected end character`); - } - } -} - -suite('markdown: Go to definition', () => { - test('Should not return definition when on link text', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[ref](#abc)`, - `[ref]: http://example.com`, - )); - - const defs = await getDefinition(doc, new vscode.Position(0, 1), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(defs, undefined); - }); - - test('Should find definition links within file from link', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, // trigger here - ``, - `[abc]: https://example.com`, - )); - - const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 2 }, - ); - }); - - test('Should find definition links using shorthand', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[ref]`, // trigger 1 - ``, - `[yes][ref]`, // trigger 2 - ``, - `[ref]: /Hello.md` // trigger 3 - )); - - { - const defs = await getDefinition(doc, new vscode.Position(0, 2), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 4 }, - ); - } - { - const defs = await getDefinition(doc, new vscode.Position(2, 7), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 4 }, - ); - } - { - const defs = await getDefinition(doc, new vscode.Position(4, 2), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 4 }, - ); - } - }); - - test('Should find definition links within file from definition', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com`, // trigger here - )); - - const defs = await getDefinition(doc, new vscode.Position(2, 3), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 2 }, - ); - }); - - test('Should not find definition links across files', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com`, - )); - - const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(workspacePath('other.md'), joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com?bad`, - )) - ])); - assertDefinitionsEqual(defs!, - { uri: docUri, line: 2 }, - ); - }); -}); diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts deleted file mode 100644 index 038ad1c55f5..00000000000 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ /dev/null @@ -1,348 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { DiagnosticComputer, DiagnosticConfiguration, DiagnosticLevel, DiagnosticManager, DiagnosticOptions } from '../languageFeatures/diagnostics'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; -import { assertRangeEqual, joinLines, workspacePath } from './util'; - - -async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: MdWorkspaceContents): Promise { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const computer = new DiagnosticComputer(engine, workspaceContents, linkProvider); - return ( - await computer.getDiagnostics(doc, { - enabled: true, - validateFileLinks: DiagnosticLevel.warning, - validateFragmentLinks: DiagnosticLevel.warning, - validateMarkdownFileLinkFragments: DiagnosticLevel.warning, - validateReferences: DiagnosticLevel.warning, - ignoreLinks: [], - }, noopToken) - ).diagnostics; -} - -function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration({})) { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - return new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration); -} - -function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actual.length, expectedRanges.length); - - for (let i = 0; i < actual.length; ++i) { - assertRangeEqual(actual[i].range, expectedRanges[i], `Range ${i} to be equal`); - } -} - -const defaultDiagnosticsOptions = Object.freeze({ - enabled: true, - validateFileLinks: DiagnosticLevel.warning, - validateMarkdownFileLinkFragments: undefined, - validateFragmentLinks: DiagnosticLevel.warning, - validateReferences: DiagnosticLevel.warning, - ignoreLinks: [], -}); - -class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { - - private readonly _onDidChange = new vscode.EventEmitter(); - public readonly onDidChange = this._onDidChange.event; - - constructor( - private readonly _options: Partial, - ) { } - - getOptions(_resource: vscode.Uri): DiagnosticOptions { - return { - ...defaultDiagnosticsOptions, - ...this._options, - }; - } -} - - -suite('markdown: Diagnostics', () => { - test('Should not return any diagnostics for empty document', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `text`, - )); - - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(diagnostics, []); - }); - - test('Should generate diagnostic for link to file that does not exist', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[bad](/no/such/file.md)`, - `[good](/doc.md)`, - `[good-ref]: /doc.md`, - `[bad-ref]: /no/such/file.md`, - )); - - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(0, 6, 0, 22), - new vscode.Range(3, 11, 3, 27), - ]); - }); - - test('Should generate diagnostics for links to header that does not exist in current file', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[good](#good-header)`, - `# Good Header`, - `[bad](#no-such-header)`, - `[good](#good-header)`, - `[good-ref]: #good-header`, - `[bad-ref]: #no-such-header`, - )); - - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(2, 6, 2, 21), - new vscode.Range(5, 11, 5, 26), - ]); - }); - - test('Should generate diagnostics for links to non-existent headers in other files', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `# My header`, - `[good](#my-header)`, - `[good](/doc1.md#my-header)`, - `[good](doc1.md#my-header)`, - `[good](/doc2.md#other-header)`, - `[bad](/doc2.md#no-such-other-header)`, - )); - - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines( - `# Other header`, - )); - - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryWorkspaceMarkdownDocuments([doc1, doc2])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(5, 6, 5, 35), - ]); - }); - - test('Should support links both with and without .md file extension', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `# My header`, - `[good](#my-header)`, - `[good](/doc.md#my-header)`, - `[good](doc.md#my-header)`, - `[good](/doc#my-header)`, - `[good](doc#my-header)`, - )); - - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should generate diagnostics for non-existent link reference', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[good link][good]`, - `[bad link][no-such]`, - ``, - `[good]: http://example.com`, - )); - - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(1, 11, 1, 18), - ]); - }); - - test('Should not generate diagnostics when validate is disabled', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](#no-such-header)`, - `[text][no-such-ref]`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ enabled: false })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should not generate diagnostics for email autolink', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `a c`, - )); - - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryWorkspaceMarkdownDocuments([doc1])); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should not generate diagnostics for html tag that looks like an autolink', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `a b c`, - `a b c`, - )); - - const diagnostics = await getComputedDiagnostics(doc1, new InMemoryWorkspaceMarkdownDocuments([doc1])); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should allow ignoring invalid file link using glob', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file)`, - `![img](/no-such-file)`, - `[text]: /no-such-file`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should be able to disable fragment validation for external files', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - - const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); - - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateMarkdownFileLinkFragments: DiagnosticLevel.ignore })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Disabling own fragment validation should also disable path fragment validation by default', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[b](#no-head)`, - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - - const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); - - { - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateFragmentLinks: DiagnosticLevel.ignore })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - } - { - // But we should be able to override the default - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 1); - } - }); - - test('ignoreLinks should allow skipping link to non-existent file', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file#header)`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('ignoreLinks should not consider link fragment', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[text](/no-such-file#header)`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('ignoreLinks should support globs', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/images/aaa.png)`, - `![i](/images/sub/bbb.png)`, - `![i](/images/sub/sub2/ccc.png)`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/images/**/*.png'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('ignoreLinks should support ignoring header', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](#no-such)`, - )); - - const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['#no-such'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('ignoreLinks should support ignoring header in file', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - - const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); - { - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md#no-such'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - } - { - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md#*'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - } - }); - - test('ignoreLinks should support ignore header links if file is ignored', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `![i](/doc2.md#no-such)`, - )); - const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); - - const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should not detect checkboxes as invalid links', async () => { - const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `- [x]`, - `- [X]`, - `- [ ]`, - )); - - const contents = new InMemoryWorkspaceMarkdownDocuments([doc1]); - const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md'] })); - const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); - assert.deepStrictEqual(diagnostics.length, 0); - }); - - test('Should detect invalid links with titles', async () => { - const doc = new InMemoryDocument(workspacePath('doc1.md'), joinLines( - `[link]( "text")`, - `[link]( 'text')`, - `[link]( (text))`, - `[link](no-such.md "text")`, - `[link](no-such.md 'text')`, - `[link](no-such.md (text))`, - )); - const diagnostics = await getComputedDiagnostics(doc, new InMemoryWorkspaceMarkdownDocuments([doc])); - assertDiagnosticsEqual(diagnostics, [ - new vscode.Range(0, 8, 0, 18), - new vscode.Range(1, 8, 1, 18), - new vscode.Range(2, 8, 2, 18), - new vscode.Range(3, 7, 3, 17), - new vscode.Range(4, 7, 4, 17), - new vscode.Range(5, 7, 5, 17), - ]); - }); -}); diff --git a/extensions/markdown-language-features/src/test/documentLink.test.ts b/extensions/markdown-language-features/src/test/documentLink.test.ts index 7c280a82bdf..37fe52e3dfb 100644 --- a/extensions/markdown-language-features/src/test/documentLink.test.ts +++ b/extensions/markdown-language-features/src/test/documentLink.test.ts @@ -24,7 +24,7 @@ function workspaceFile(...segments: string[]) { async function getLinksForFile(file: vscode.Uri): Promise { debugLog('getting links', file.toString(), Date.now()); - const r = (await vscode.commands.executeCommand('vscode.executeLinkProvider', file))!; + const r = (await vscode.commands.executeCommand('vscode.executeLinkProvider', file, /*linkResolveCount*/ 100))!; debugLog('got links', file.toString(), Date.now()); return r; } @@ -134,7 +134,7 @@ async function getLinksForFile(file: vscode.Uri): Promise } }); - test('Should navigate to fragment within current untitled file', async () => { + test('Should navigate to fragment within current untitled file', async () => { // TODO: skip for now for ls migration const testFile = workspaceFile('x.md').with({ scheme: 'untitled' }); await withFileContents(testFile, joinLines( '[](#second)', @@ -171,7 +171,7 @@ async function withFileContents(file: vscode.Uri, contents: string): Promise { - test('Should not return anything for empty document', async () => { - const links = await getLinksForFile(''); - assert.strictEqual(links.length, 0); - }); - - test('Should not return anything for simple document without links', async () => { - const links = await getLinksForFile(joinLines( - '# a', - 'fdasfdfsafsa', - )); - assert.strictEqual(links.length, 0); - }); - - test('Should detect basic http links', async () => { - const links = await getLinksForFile('a [b](https://example.com) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 25) - ]); - }); - - test('Should detect basic workspace links', async () => { - { - const links = await getLinksForFile('a [b](./file) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 12) - ]); - } - { - const links = await getLinksForFile('a [b](file.png) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 14) - ]); - } - }); - - test('Should detect links with title', async () => { - const links = await getLinksForFile('a [b](https://example.com "abc") c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 25) - ]); - }); - - test('Should handle links with escaped characters in name (#35245)', async () => { - const links = await getLinksForFile('a [b\\]](./file)'); - assertLinksEqual(links, [ - new vscode.Range(0, 8, 0, 14) - ]); - }); - - test('Should handle links with balanced parens', async () => { - { - const links = await getLinksForFile('a [b](https://example.com/a()c) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 30) - ]); - } - { - const links = await getLinksForFile('a [b](https://example.com/a(b)c) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 31) - ]); - } - { - // #49011 - const links = await getLinksForFile('[A link](http://ThisUrlhasParens/A_link(in_parens))'); - assertLinksEqual(links, [ - new vscode.Range(0, 9, 0, 50) - ]); - } - }); - - test('Should ignore texts in brackets inside link title (#150921)', async () => { - { - const links = await getLinksForFile('[some [inner bracket pairs] in title]()'); - assertLinksEqual(links, [ - new vscode.Range(0, 39, 0, 43), - ]); - } - { - const links = await getLinksForFile('[some [inner bracket pairs] in title](link)'); - assertLinksEqual(links, [ - new vscode.Range(0, 38, 0, 42) - ]); - } - }); - - test('Should handle two links without space', async () => { - const links = await getLinksForFile('a ([test](test)[test2](test2)) c'); - assertLinksEqual(links, [ - new vscode.Range(0, 10, 0, 14), - new vscode.Range(0, 23, 0, 28) - ]); - }); - - test('should handle hyperlinked images (#49238)', async () => { - { - const links = await getLinksForFile('[![alt text](image.jpg)](https://example.com)'); - assertLinksEqual(links, [ - new vscode.Range(0, 13, 0, 22), - new vscode.Range(0, 25, 0, 44) - ]); - } - { - const links = await getLinksForFile('[![a]( whitespace.jpg )]( https://whitespace.com )'); - assertLinksEqual(links, [ - new vscode.Range(0, 7, 0, 21), - new vscode.Range(0, 26, 0, 48) - ]); - } - { - const links = await getLinksForFile('[![a](img1.jpg)](file1.txt) text [![a](img2.jpg)](file2.txt)'); - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 14), - new vscode.Range(0, 17, 0, 26), - new vscode.Range(0, 39, 0, 47), - new vscode.Range(0, 50, 0, 59), - ]); - } - }); - - test('Should not consider link references starting with ^ character valid (#107471)', async () => { - const links = await getLinksForFile('[^reference]: https://example.com'); - assert.strictEqual(links.length, 0); - }); - - test('Should find definitions links with spaces in angle brackets (#136073)', async () => { - const links = await getLinksForFile(joinLines( - '[a]: ', - '[b]: ', - )); - - assertLinksEqual(links, [ - new vscode.Range(0, 6, 0, 9), - new vscode.Range(1, 6, 1, 8), - ]); - }); - - test('Should only find one link for reference sources [a]: source (#141285)', async () => { - const links = await getLinksForFile(joinLines( - '[Works]: https://example.com', - )); - - assert.strictEqual(links.length, 1); - }); - - test('Should find reference link shorthand (#141285)', async () => { - let links = await getLinksForFile(joinLines( - '[ref]', - '[ref]: https://example.com', - )); - assert.strictEqual(links.length, 2); - - links = await getLinksForFile(joinLines( - '[Does Not Work]', - '[def]: https://example.com', - )); - assert.strictEqual(links.length, 1); - }); - - test('Should not include reference link shorthand when source does not exist (#141285)', async () => { - const links = await getLinksForFile('[Works]'); - assert.strictEqual(links.length, 0); - }); - - test('Should not include reference links with escaped leading brackets', async () => { - const links = await getLinksForFile(joinLines( - `\\[bad link][good]`, - `\\[good]`, - `[good]: http://example.com`, - )); - assertLinksEqual(links, [ - new vscode.Range(2, 8, 2, 26) // Should only find the definition - ]); - }); - - test('Should not consider links in code fenced with backticks', async () => { - const links = await getLinksForFile(joinLines( - '```', - '[b](https://example.com)', - '```')); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links in code fenced with tilde', async () => { - const links = await getLinksForFile(joinLines( - '~~~', - '[b](https://example.com)', - '~~~')); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links in indented code', async () => { - const links = await getLinksForFile(' [b](https://example.com)'); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links in inline code span', async () => { - const links = await getLinksForFile('`[b](https://example.com)`'); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links with code span inside', async () => { - const links = await getLinksForFile('[li`nk](https://example.com`)'); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links in multiline inline code span', async () => { - const links = await getLinksForFile(joinLines( - '`` ', - '[b](https://example.com)', - '``')); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider link references in code fenced with backticks (#146714)', async () => { - const links = await getLinksForFile(joinLines( - '```', - '[a] [bb]', - '```')); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider reference sources in code fenced with backticks (#146714)', async () => { - const links = await getLinksForFile(joinLines( - '```', - '[a]: http://example.com;', - '[b]: ;', - '[c]: (http://example.com);', - '```')); - assert.strictEqual(links.length, 0); - }); - - test('Should not consider links in multiline inline code span between between text', async () => { - const links = await getLinksForFile(joinLines( - '[b](https://1.com) `[b](https://2.com)', - '` [b](https://3.com)')); - assert.deepStrictEqual(links.map(l => l.target?.authority), ['1.com', '3.com']); - }); - - test('Should not consider links in multiline inline code span with new line after the first backtick', async () => { - const links = await getLinksForFile(joinLines( - '`', - '[b](https://example.com)`')); - assert.strictEqual(links.length, 0); - }); - - test('Should not miss links in invalid multiline inline code span', async () => { - const links = await getLinksForFile(joinLines( - '`` ', - '', - '[b](https://example.com)', - '', - '``')); - assert.strictEqual(links.length, 1); - }); - - test('Should find autolinks', async () => { - const links = await getLinksForFile('pre post'); - assertLinksEqual(links, [ - new vscode.Range(0, 5, 0, 23) - ]); - }); - - test('Should not detect links inside html comment blocks', async () => { - const links = await getLinksForFile(joinLines( - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - ``, - )); - assert.strictEqual(links.length, 0); - }); - - test.skip('Should not detect links inside inline html comments', async () => { - // See #149678 - const links = await getLinksForFile(joinLines( - `text text`, - `text text`, - `text text`, - ``, - `text text`, - ``, - `text text`, - ``, - `text text`, - )); - assert.strictEqual(links.length, 0); - }); - - test('Should not mark checkboxes as links', async () => { - const links = await getLinksForFile(joinLines( - '- [x]', - '- [X]', - '- [ ]', - '* [x]', - '* [X]', - '* [ ]', - ``, - `[x]: http://example.com` - )); - assertLinksEqual(links, [ - new vscode.Range(7, 5, 7, 23) - ]); - }); - - test('Should still find links on line with checkbox', async () => { - const links = await getLinksForFile(joinLines( - '- [x] [x]', - '- [X] [x]', - '- [] [x]', - ``, - `[x]: http://example.com` - )); - - assertLinksEqual(links, [ - new vscode.Range(0, 7, 0, 8), - new vscode.Range(1, 7, 1, 8), - new vscode.Range(2, 6, 2, 7), - new vscode.Range(4, 5, 4, 23), - ]); - }); - - test('Should find link only within angle brackets.', async () => { - const links = await getLinksForFile(joinLines( - `[link]()` - )); - assertLinksEqual(links, [new vscode.Range(0, 8, 0, 12)]); - }); - - test('Should find link within angle brackets even with link title.', async () => { - const links = await getLinksForFile(joinLines( - `[link]( "test title")` - )); - assertLinksEqual(links, [new vscode.Range(0, 8, 0, 12)]); - }); - - test('Should find link within angle brackets even with surrounding spaces.', async () => { - const links = await getLinksForFile(joinLines( - `[link]( )` - )); - assertLinksEqual(links, [new vscode.Range(0, 9, 0, 13)]); - }); - - test('Should find link within angle brackets for image hyperlinks.', async () => { - const links = await getLinksForFile(joinLines( - `![link]()` - )); - assertLinksEqual(links, [new vscode.Range(0, 9, 0, 13)]); - }); - - test('Should find link with spaces in angle brackets for image hyperlinks with titles.', async () => { - const links = await getLinksForFile(joinLines( - `![link](< path > "test")` - )); - assertLinksEqual(links, [new vscode.Range(0, 9, 0, 15)]); - }); - - - test('Should not find link due to incorrect angle bracket notation or usage.', async () => { - const links = await getLinksForFile(joinLines( - `[link]( path>)`, - `[link](> path)`, - )); - assert.strictEqual(links.length, 0); - }); - - test('Should find link within angle brackets even with space inside link.', async () => { - - const links = await getLinksForFile(joinLines( - `[link]()` - )); - - assertLinksEqual(links, [new vscode.Range(0, 8, 0, 13)]); - }); - - test('Should find links with titles', async () => { - const links = await getLinksForFile(joinLines( - `[link]( "text")`, - `[link]( 'text')`, - `[link]( (text))`, - `[link](no-such.md "text")`, - `[link](no-such.md 'text')`, - `[link](no-such.md (text))`, - )); - assertLinksEqual(links, [ - new vscode.Range(0, 8, 0, 18), - new vscode.Range(1, 8, 1, 18), - new vscode.Range(2, 8, 2, 18), - new vscode.Range(3, 7, 3, 17), - new vscode.Range(4, 7, 4, 17), - new vscode.Range(5, 7, 5, 17), - ]); - }); -}); diff --git a/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts deleted file mode 100644 index e29c7399e7a..00000000000 --- a/extensions/markdown-language-features/src/test/documentSymbolProvider.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryDocument } from '../util/inMemoryDocument'; - - -const testFileName = vscode.Uri.file('test.md'); - - -function getSymbolsForFile(fileContents: string) { - const doc = new InMemoryDocument(testFileName, fileContents); - const provider = new MdDocumentSymbolProvider(createNewMarkdownEngine()); - return provider.provideDocumentSymbols(doc); -} - - -suite('markdown.DocumentSymbolProvider', () => { - test('Should not return anything for empty document', async () => { - const symbols = await getSymbolsForFile(''); - assert.strictEqual(symbols.length, 0); - }); - - test('Should not return anything for document with no headers', async () => { - const symbols = await getSymbolsForFile('a\na'); - assert.strictEqual(symbols.length, 0); - }); - - test('Should not return anything for document with # but no real headers', async () => { - const symbols = await getSymbolsForFile('a#a\na#'); - assert.strictEqual(symbols.length, 0); - }); - - test('Should return single symbol for single header', async () => { - const symbols = await getSymbolsForFile('# h'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - }); - - test('Should not care about symbol level for single header', async () => { - const symbols = await getSymbolsForFile('### h'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '### h'); - }); - - test('Should put symbols of same level in flat list', async () => { - const symbols = await getSymbolsForFile('## h\n## h2'); - assert.strictEqual(symbols.length, 2); - assert.strictEqual(symbols[0].name, '## h'); - assert.strictEqual(symbols[1].name, '## h2'); - }); - - test('Should nest symbol of level - 1 under parent', async () => { - - const symbols = await getSymbolsForFile('# h\n## h2\n## h3'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 2); - assert.strictEqual(symbols[0].children[0].name, '## h2'); - assert.strictEqual(symbols[0].children[1].name, '## h3'); - }); - - test('Should nest symbol of level - n under parent', async () => { - const symbols = await getSymbolsForFile('# h\n#### h2'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 1); - assert.strictEqual(symbols[0].children[0].name, '#### h2'); - }); - - test('Should flatten children where lower level occurs first', async () => { - const symbols = await getSymbolsForFile('# h\n### h2\n## h3'); - assert.strictEqual(symbols.length, 1); - assert.strictEqual(symbols[0].name, '# h'); - assert.strictEqual(symbols[0].children.length, 2); - assert.strictEqual(symbols[0].children[0].name, '### h2'); - assert.strictEqual(symbols[0].children[1].name, '## h3'); - }); - - test('Should handle line separator in file. Issue #63749', async () => { - const symbols = await getSymbolsForFile(`# A -- foo - -# B -- bar`); - assert.strictEqual(symbols.length, 2); - assert.strictEqual(symbols[0].name, '# A'); - assert.strictEqual(symbols[1].name, '# B'); - }); -}); - diff --git a/extensions/markdown-language-features/src/test/engine.test.ts b/extensions/markdown-language-features/src/test/engine.test.ts index c314e24d748..424844d1ad7 100644 --- a/extensions/markdown-language-features/src/test/engine.test.ts +++ b/extensions/markdown-language-features/src/test/engine.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { createNewMarkdownEngine } from './engine'; import { InMemoryDocument } from '../util/inMemoryDocument'; +import { createNewMarkdownEngine } from './engine'; const testFileName = vscode.Uri.file('test.md'); @@ -30,22 +30,23 @@ suite('markdown.engine', () => { }); }); - suite('image-caching', () => { + suite.only('image-caching', () => { const input = '![](img.png) [](no-img.png) ![](http://example.org/img.png) ![](img.png) ![](./img2.png)'; test('Extracts all images', async () => { const engine = createNewMarkdownEngine(); - assert.deepStrictEqual((await engine.render(input)), { - html: '

' - + ' ' - + ' ' - + ' ' - + ' ' - + '' - + '

\n' - , - containingImages: [{ src: 'img.png' }, { src: 'http://example.org/img.png' }, { src: 'img.png' }, { src: './img2.png' }], - }); + const result = await engine.render(input); + assert.deepStrictEqual(result.html, + '

' + + ' ' + + ' ' + + ' ' + + ' ' + + '' + + '

\n' + ); + + assert.deepStrictEqual([...result.containingImages], ['img.png', 'http://example.org/img.png', './img2.png']); }); }); }); diff --git a/extensions/markdown-language-features/src/test/engine.ts b/extensions/markdown-language-features/src/test/engine.ts index e75be4abac5..46fdc02ee78 100644 --- a/extensions/markdown-language-features/src/test/engine.ts +++ b/extensions/markdown-language-features/src/test/engine.ts @@ -4,10 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { MarkdownEngine } from '../markdownEngine'; +import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions'; import { githubSlugifier } from '../slugify'; import { Disposable } from '../util/dispose'; +import { nulLogger } from './nulLogging'; const emptyContributions = new class extends Disposable implements MarkdownContributionProvider { readonly extensionUri = vscode.Uri.file('/'); @@ -15,6 +16,6 @@ const emptyContributions = new class extends Disposable implements MarkdownContr readonly onContributionsChanged = this._register(new vscode.EventEmitter()).event; }; -export function createNewMarkdownEngine(): MarkdownEngine { - return new MarkdownEngine(emptyContributions, githubSlugifier); +export function createNewMarkdownEngine(): MarkdownItEngine { + return new MarkdownItEngine(emptyContributions, githubSlugifier, nulLogger); } diff --git a/extensions/markdown-language-features/src/test/fileReferences.test.ts b/extensions/markdown-language-features/src/test/fileReferences.test.ts deleted file mode 100644 index 7857f7364f9..00000000000 --- a/extensions/markdown-language-features/src/test/fileReferences.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { MdReference, MdReferencesProvider } from '../languageFeatures/references'; -import { githubSlugifier } from '../slugify'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; -import { joinLines, workspacePath } from './util'; - - -function getFileReferences(resource: vscode.Uri, workspaceContents: MdWorkspaceContents) { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - return provider.getAllReferencesToFile(resource, noopToken); -} - -function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) { - assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`); - - for (let i = 0; i < actualRefs.length; ++i) { - const actual = actualRefs[i].location; - const expected = expectedRefs[i]; - assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`); - assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`); - assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`); - } -} - -suite('markdown: find file references', () => { - - test('Should find basic references', async () => { - const docUri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(docUri, joinLines( - `# header`, - `[link 1](./other.md)`, - `[link 2](./other.md)`, - )), - new InMemoryDocument(otherUri, joinLines( - `# header`, - `pre`, - `[link 3](./other.md)`, - `post`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 1 }, - { uri: docUri, line: 2 }, - { uri: otherUri, line: 2 }, - ); - }); - - test('Should find references with and without file extensions', async () => { - const docUri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(docUri, joinLines( - `# header`, - `[link 1](./other.md)`, - `[link 2](./other)`, - )), - new InMemoryDocument(otherUri, joinLines( - `# header`, - `pre`, - `[link 3](./other.md)`, - `[link 4](./other)`, - `post`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 1 }, - { uri: docUri, line: 2 }, - { uri: otherUri, line: 2 }, - { uri: otherUri, line: 3 }, - ); - }); - - test('Should find references with headers on links', async () => { - const docUri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - - const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(docUri, joinLines( - `# header`, - `[link 1](./other.md#sub-bla)`, - `[link 2](./other#sub-bla)`, - )), - new InMemoryDocument(otherUri, joinLines( - `# header`, - `pre`, - `[link 3](./other.md#sub-bla)`, - `[link 4](./other#sub-bla)`, - `post`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 1 }, - { uri: docUri, line: 2 }, - { uri: otherUri, line: 2 }, - { uri: otherUri, line: 3 }, - ); - }); -}); diff --git a/extensions/markdown-language-features/src/test/foldingProvider.test.ts b/extensions/markdown-language-features/src/test/foldingProvider.test.ts deleted file mode 100644 index bfb78f8a77b..00000000000 --- a/extensions/markdown-language-features/src/test/foldingProvider.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdFoldingProvider } from '../languageFeatures/foldingProvider'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; -import { joinLines } from './util'; - -const testFileName = vscode.Uri.file('test.md'); - -suite('markdown.FoldingProvider', () => { - test('Should not return anything for empty document', async () => { - const folds = await getFoldsForDocument(``); - assert.strictEqual(folds.length, 0); - }); - - test('Should not return anything for document without headers', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `**b** afas`, - `a#b`, - `a`, - )); - assert.strictEqual(folds.length, 0); - }); - - test('Should fold from header to end of document', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `# b`, - `c`, - `d`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('Should leave single newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - ``, - `# b`, - `y`, - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should collapse multiple newlines to single newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - ``, - ``, - ``, - `# b`, - `y` - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 4); - }); - - test('Should not collapse if there is no newline before next header', async () => { - const folds = await getFoldsForDocument(joinLines( - ``, - `# a`, - `x`, - `# b`, - `y`, - )); - assert.strictEqual(folds.length, 2); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold nested markers', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - ``, - `b`, - ``, - `b.a`, - ``, - `b`, - ``, - `b.b`, - ``, - `b`, - ``, - `a`, - )); - assert.strictEqual(folds.length, 3); - const [outer, first, second] = folds.sort((a, b) => a.start - b.start); - - assert.strictEqual(outer.start, 1); - assert.strictEqual(outer.end, 11); - assert.strictEqual(first.start, 3); - assert.strictEqual(first.end, 5); - assert.strictEqual(second.start, 7); - assert.strictEqual(second.end, 9); - }); - - test('Should fold from list to end of document', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `- b`, - `c`, - `d`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('lists folds should span multiple lines of content', async () => { - const folds = await getFoldsForDocument(joinLines( - `a`, - `- This list item\n spans multiple\n lines.`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('List should leave single blankline before new element', async () => { - const folds = await getFoldsForDocument(joinLines( - `- a`, - `a`, - ``, - ``, - `b` - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 0); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold fenced code blocks', async () => { - const folds = await getFoldsForDocument(joinLines( - `~~~ts`, - `a`, - `~~~`, - `b`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 0); - assert.strictEqual(firstFold.end, 2); - }); - - test('Should fold fenced code blocks with yaml front matter', async () => { - const folds = await getFoldsForDocument(joinLines( - `---`, - `title: bla`, - `---`, - ``, - `~~~ts`, - `a`, - `~~~`, - ``, - `a`, - `a`, - `b`, - `a`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 4); - assert.strictEqual(firstFold.end, 6); - }); - - test('Should fold html blocks', async () => { - const folds = await getFoldsForDocument(joinLines( - `x`, - `
`, - ` fa`, - `
`, - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - }); - - test('Should fold html block comments', async () => { - const folds = await getFoldsForDocument(joinLines( - `x`, - `` - )); - assert.strictEqual(folds.length, 1); - const firstFold = folds[0]; - assert.strictEqual(firstFold.start, 1); - assert.strictEqual(firstFold.end, 3); - assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment); - }); -}); - - -async function getFoldsForDocument(contents: string) { - const doc = new InMemoryDocument(testFileName, contents); - const provider = new MdFoldingProvider(createNewMarkdownEngine()); - return await provider.provideFoldingRanges(doc, {}, new vscode.CancellationTokenSource().token); -} diff --git a/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts b/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts index 53dab1a3e6d..a383a73335a 100644 --- a/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts +++ b/extensions/markdown-language-features/src/test/inMemoryWorkspace.ts @@ -4,47 +4,72 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { ITextDocument } from '../types/textDocument'; +import { Disposable } from '../util/dispose'; import { ResourceMap } from '../util/resourceMap'; -import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents'; +import { IMdWorkspace } from '../workspace'; -export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents { - private readonly _documents = new ResourceMap(uri => uri.fsPath); +export class InMemoryMdWorkspace extends Disposable implements IMdWorkspace { + private readonly _documents = new ResourceMap(uri => uri.fsPath); - constructor(documents: SkinnyTextDocument[]) { + constructor(documents: ITextDocument[]) { + super(); for (const doc of documents) { this._documents.set(doc.uri, doc); } } - public async getAllMarkdownDocuments() { + public values() { return Array.from(this._documents.values()); } - public async getMarkdownDocument(resource: vscode.Uri): Promise { + public async getAllMarkdownDocuments() { + return this.values(); + } + + public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise { return this._documents.get(resource); } + public hasMarkdownDocument(resolvedHrefPath: vscode.Uri): boolean { + return this._documents.has(resolvedHrefPath); + } + public async pathExists(resource: vscode.Uri): Promise { return this._documents.has(resource); } - private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter(); + public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> { + const files = new Map(); + const pathPrefix = resource.fsPath + (resource.fsPath.endsWith('/') || resource.fsPath.endsWith('\\') ? '' : path.sep); + for (const doc of this._documents.values()) { + const path = doc.uri.fsPath; + if (path.startsWith(pathPrefix)) { + const parts = path.slice(pathPrefix.length).split(/\/|\\/g); + files.set(parts[0], parts.length > 1 ? vscode.FileType.Directory : vscode.FileType.File); + } + } + return Array.from(files.entries()); + } + + private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event; - private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event; - private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event; - public updateDocument(document: SkinnyTextDocument) { + public updateDocument(document: ITextDocument) { this._documents.set(document.uri, document); this._onDidChangeMarkdownDocumentEmitter.fire(document); } - public createDocument(document: SkinnyTextDocument) { + public createDocument(document: ITextDocument) { assert.ok(!this._documents.has(document.uri)); this._documents.set(document.uri, document); diff --git a/extensions/markdown-language-features/src/test/nulLogging.ts b/extensions/markdown-language-features/src/test/nulLogging.ts new file mode 100644 index 00000000000..a786ab83b4f --- /dev/null +++ b/extensions/markdown-language-features/src/test/nulLogging.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogger } from '../logging'; + +export const nulLogger = new class implements ILogger { + verbose(): void { + // noop + } +}; diff --git a/extensions/markdown-language-features/src/test/pathCompletion.test.ts b/extensions/markdown-language-features/src/test/pathCompletion.test.ts deleted file mode 100644 index c5889627167..00000000000 --- a/extensions/markdown-language-features/src/test/pathCompletion.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { MdPathCompletionProvider } from '../languageFeatures/pathCompletions'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { createNewMarkdownEngine } from './engine'; -import { CURSOR, getCursorPositions, joinLines, workspacePath } from './util'; - - -function getCompletionsAtCursor(resource: vscode.Uri, fileContents: string) { - const doc = new InMemoryDocument(resource, fileContents); - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const provider = new MdPathCompletionProvider(engine, linkProvider); - const cursorPositions = getCursorPositions(fileContents, doc); - return provider.provideCompletionItems(doc, cursorPositions[0], noopToken, { - triggerCharacter: undefined, - triggerKind: vscode.CompletionTriggerKind.Invoke, - }); -} - -suite('Markdown path completion provider', () => { - - setup(async () => { - // These tests assume that the markdown completion provider is already registered - await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate(); - }); - - test('Should not return anything when triggered in empty doc', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), `${CURSOR}`); - assert.strictEqual(completions.length, 0); - }); - - test('Should return anchor completions', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](#${CURSOR}`, - ``, - `# A b C`, - `# x y Z`, - )); - - assert.strictEqual(completions.length, 2); - assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has a-b-c anchor completion'); - assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has x-y-z anchor completion'); - }); - - test('Should not return suggestions for http links', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](http:${CURSOR}`, - ``, - `# http`, - `# http:`, - `# https:`, - )); - - assert.strictEqual(completions.length, 0); - }); - - test('Should return relative path suggestions', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](${CURSOR}`, - ``, - `# A b C`, - )); - - assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion'); - assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion'); - assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion'); - }); - - test('Should return relative path suggestions using ./', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](./${CURSOR}`, - ``, - `# A b C`, - )); - - assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion'); - assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion'); - assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion'); - }); - - test('Should return absolute path suggestions using /', async () => { - const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines( - `[](/${CURSOR}`, - ``, - `# A b C`, - )); - - assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion'); - assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion'); - assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion'); - assert.ok(!completions.some(x => x.label === 'c.md'), 'Should not have c.md from sub folder'); - }); - - test('Should return anchor suggestions in other file', async () => { - const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines( - `[](/b.md#${CURSOR}`, - )); - - assert.ok(completions.some(x => x.label === '#b'), 'Has #b header completion'); - assert.ok(completions.some(x => x.label === '#header1'), 'Has #header1 header completion'); - }); - - test('Should reference links for current file', async () => { - const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines( - `[][${CURSOR}`, - ``, - `[ref-1]: bla`, - `[ref-2]: bla`, - )); - - assert.strictEqual(completions.length, 2); - assert.ok(completions.some(x => x.label === 'ref-1'), 'Has ref-1 reference completion'); - assert.ok(completions.some(x => x.label === 'ref-2'), 'Has ref-2 reference completion'); - }); - - test('Should complete headers in link definitions', async () => { - const completions = await getCompletionsAtCursor(workspacePath('sub', 'new.md'), joinLines( - `# a B c`, - `# x y Z`, - `[ref-1]: ${CURSOR}`, - )); - - assert.ok(completions.some(x => x.label === '#a-b-c'), 'Has #a-b-c header completion'); - assert.ok(completions.some(x => x.label === '#x-y-z'), 'Has #x-y-z header completion'); - }); - - test('Should complete relative paths in link definitions', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `# a B c`, - `[ref-1]: ${CURSOR}`, - )); - - assert.ok(completions.some(x => x.label === 'a.md'), 'Has a.md file completion'); - assert.ok(completions.some(x => x.label === 'b.md'), 'Has b.md file completion'); - assert.ok(completions.some(x => x.label === 'sub/'), 'Has sub folder completion'); - }); - - test('Should escape spaces in path names', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](./sub/${CURSOR})` - )); - - assert.ok(completions.some(x => x.insertText === 'file%20with%20space.md'), 'Has encoded path completion'); - }); - - test('Should complete paths for path with encoded spaces', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[](./sub%20with%20space/${CURSOR})` - )); - - assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space'); - }); - - test('Should complete definition path for path with encoded spaces', async () => { - const completions = await getCompletionsAtCursor(workspacePath('new.md'), joinLines( - `[def]: ./sub%20with%20space/${CURSOR}` - )); - - assert.ok(completions.some(x => x.insertText === 'file.md'), 'Has file from space'); - }); -}); diff --git a/extensions/markdown-language-features/src/test/references.test.ts b/extensions/markdown-language-features/src/test/references.test.ts deleted file mode 100644 index 5280a72e308..00000000000 --- a/extensions/markdown-language-features/src/test/references.test.ts +++ /dev/null @@ -1,595 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { MdReferencesProvider } from '../languageFeatures/references'; -import { githubSlugifier } from '../slugify'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; -import { joinLines, workspacePath } from './util'; - - -function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken); -} - -function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expectedRefs: { uri: vscode.Uri; line: number; startCharacter?: number; endCharacter?: number }[]) { - assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`); - - for (let i = 0; i < actualRefs.length; ++i) { - const actual = actualRefs[i]; - const expected = expectedRefs[i]; - assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`); - assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`); - assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`); - if (typeof expected.startCharacter !== 'undefined') { - assert.strictEqual(actual.range.start.character, expected.startCharacter, `Ref '${i}' has expected start character`); - } - if (typeof expected.endCharacter !== 'undefined') { - assert.strictEqual(actual.range.end.character, expected.endCharacter, `Ref '${i}' has expected end character`); - } - } -} - -suite('markdown: find all references', () => { - test('Should not return references when not on header or link', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - `text`, - )); - - { - const refs = await getReferences(doc, new vscode.Position(1, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs, []); - } - { - const refs = await getReferences(doc, new vscode.Position(3, 2), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs, []); - } - }); - - test('Should find references from header within same file', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - `[not link](#noabc)`, - `[link 2](#abc)`, - )); - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, - { uri, line: 2 }, - { uri, line: 4 }, - ); - }); - - test('Should not return references when on link text', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `[ref](#abc)`, - `[ref]: http://example.com`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 1), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs, []); - }); - - test('Should find references using normalized slug', async () => { - const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines( - `# a B c`, - `[simple](#a-b-c)`, - `[start underscore](#_a-b-c)`, - `[different case](#a-B-C)`, - )); - - { - // Trigger header - const refs = await getReferences(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs!.length, 4); - } - { - // Trigger on line 1 - const refs = await getReferences(doc, new vscode.Position(1, 12), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs!.length, 4); - } - { - // Trigger on line 2 - const refs = await getReferences(doc, new vscode.Position(2, 24), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs!.length, 4); - } - { - // Trigger on line 3 - const refs = await getReferences(doc, new vscode.Position(3, 20), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.deepStrictEqual(refs!.length, 4); - } - }); - - test('Should find references from header across files', async () => { - const docUri = workspacePath('doc.md'); - const other1Uri = workspacePath('sub', 'other.md'); - const other2Uri = workspacePath('other2.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - )); - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(other1Uri, joinLines( - `[not link](#abc)`, - `[not link](/doc.md#abz)`, - `[link](/doc.md#abc)`, - )), - new InMemoryDocument(other2Uri, joinLines( - `[not link](#abc)`, - `[not link](./doc.md#abz)`, - `[link](./doc.md#abc)`, - )) - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, // Header definition - { uri: docUri, line: 2 }, - { uri: other1Uri, line: 2 }, - { uri: other2Uri, line: 2 }, - ); - }); - - test('Should find references from header to link definitions ', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# abc`, - ``, - `[bla]: #abc` - )); - - const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, // Header definition - { uri, line: 2 }, - ); - }); - - test('Should find header references from link definition', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# A b C`, - `[text][bla]`, - `[bla]: #a-b-c`, // trigger here - )); - - const refs = await getReferences(doc, new vscode.Position(2, 9), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, // Header definition - { uri, line: 2 }, - ); - }); - - test('Should find references from link within same file', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - `[not link](#noabc)`, - `[link 2](#abc)`, - )); - - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, // Header definition - { uri, line: 2 }, - { uri, line: 4 }, - ); - }); - - test('Should find references from link across files', async () => { - const docUri = workspacePath('doc.md'); - const other1Uri = workspacePath('sub', 'other.md'); - const other2Uri = workspacePath('other2.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - )); - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(other1Uri, joinLines( - `[not link](#abc)`, - `[not link](/doc.md#abz)`, - `[with ext](/doc.md#abc)`, - `[without ext](/doc#abc)`, - )), - new InMemoryDocument(other2Uri, joinLines( - `[not link](#abc)`, - `[not link](./doc.md#abz)`, - `[link](./doc.md#abc)`, - )) - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, // Header definition - { uri: docUri, line: 2 }, - { uri: other1Uri, line: 2 }, // Other with ext - { uri: other1Uri, line: 3 }, // Other without ext - { uri: other2Uri, line: 2 }, // Other2 - ); - }); - - test('Should find references without requiring file extensions', async () => { - const docUri = workspacePath('doc.md'); - const other1Uri = workspacePath('other.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `# a B c`, - ``, - `[link 1](#a-b-c)`, - )); - const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(other1Uri, joinLines( - `[not link](#a-b-c)`, - `[not link](/doc.md#a-b-z)`, - `[with ext](/doc.md#a-b-c)`, - `[without ext](/doc#a-b-c)`, - `[rel with ext](./doc.md#a-b-c)`, - `[rel without ext](./doc#a-b-c)`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, // Header definition - { uri: docUri, line: 2 }, - { uri: other1Uri, line: 2 }, // Other with ext - { uri: other1Uri, line: 3 }, // Other without ext - { uri: other1Uri, line: 4 }, // Other relative link with ext - { uri: other1Uri, line: 5 }, // Other relative link without ext - ); - }); - - test('Should find references from link across files when triggered on link without file extension', async () => { - const docUri = workspacePath('doc.md'); - const other1Uri = workspacePath('sub', 'other.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `[with ext](./sub/other#header)`, - `[without ext](./sub/other.md#header)`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 23), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(other1Uri, joinLines( - `pre`, - `# header`, - `post`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: other1Uri, line: 1 }, // Header definition - { uri: docUri, line: 0 }, - { uri: docUri, line: 1 }, - ); - }); - - test('Should include header references when triggered on file link', async () => { - const docUri = workspacePath('doc.md'); - const otherUri = workspacePath('sub', 'other.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `[with ext](./sub/other)`, - `[with ext](./sub/other#header)`, - `[without ext](./sub/other.md#no-such-header)`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(otherUri, joinLines( - `pre`, - `# header`, // Definition should not be included since we triggered on a file link - `post`, - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 1 }, - { uri: docUri, line: 2 }, - ); - }); - - test('Should not include refs from other file to own header', async () => { - const docUri = workspacePath('doc.md'); - const otherUri = workspacePath('sub', 'other.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `[other](./sub/other)`, // trigger here - )); - - const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(otherUri, joinLines( - `# header`, // Definition should not be included since we triggered on a file link - `[text](#header)`, // Ref should not be included since it is to own file - )), - ])); - - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - ); - }); - - test('Should find explicit references to own file ', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[bare](doc.md)`, // trigger here - `[rel](./doc.md)`, - `[abs](/doc.md)`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, - { uri, line: 1 }, - { uri, line: 2 }, - ); - }); - - test('Should support finding references to http uri', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[1](http://example.com)`, - `[no](https://example.com)`, - `[2](http://example.com)`, - `[3]: http://example.com`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, - { uri, line: 2 }, - { uri, line: 3 }, - ); - }); - - test('Should consider authority, scheme and paths when finding references to http uri', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[1](http://example.com/cat)`, - `[2](http://example.com)`, - `[3](http://example.com/dog)`, - `[4](http://example.com/cat/looong)`, - `[5](http://example.com/cat)`, - `[6](http://other.com/cat)`, - `[7](https://example.com/cat)`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, - { uri, line: 4 }, - ); - }); - - test('Should support finding references to http uri across files', async () => { - const uri1 = workspacePath('doc.md'); - const uri2 = workspacePath('doc2.md'); - const doc = new InMemoryDocument(uri1, joinLines( - `[1](http://example.com)`, - `[3]: http://example.com`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(uri2, joinLines( - `[other](http://example.com)`, - )) - ])); - assertReferencesEqual(refs!, - { uri: uri1, line: 0 }, - { uri: uri1, line: 1 }, - { uri: uri2, line: 0 }, - ); - }); - - test('Should support finding references to autolinked http links', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[1](http://example.com)`, - ``, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri, line: 0 }, - { uri, line: 1 }, - ); - }); - - test('Should distinguish between references to file and to header within file', async () => { - const docUri = workspacePath('doc.md'); - const other1Uri = workspacePath('sub', 'other.md'); - - const doc = new InMemoryDocument(docUri, joinLines( - `# abc`, - ``, - `[link 1](#abc)`, - )); - const otherDoc = new InMemoryDocument(other1Uri, joinLines( - `[link](/doc.md#abc)`, - `[link no text](/doc#abc)`, - )); - const workspaceContents = new InMemoryWorkspaceMarkdownDocuments([ - doc, - otherDoc, - ]); - { - // Check refs to header fragment - const headerRefs = await getReferences(otherDoc, new vscode.Position(0, 16), workspaceContents); - assertReferencesEqual(headerRefs!, - { uri: docUri, line: 0 }, // Header definition - { uri: docUri, line: 2 }, - { uri: other1Uri, line: 0 }, - { uri: other1Uri, line: 1 }, - ); - } - { - // Check refs to file itself from link with ext - const fileRefs = await getReferences(otherDoc, new vscode.Position(0, 9), workspaceContents); - assertReferencesEqual(fileRefs!, - { uri: other1Uri, line: 0, endCharacter: 14 }, - { uri: other1Uri, line: 1, endCharacter: 19 }, - ); - } - { - // Check refs to file itself from link without ext - const fileRefs = await getReferences(otherDoc, new vscode.Position(1, 17), workspaceContents); - assertReferencesEqual(fileRefs!, - { uri: other1Uri, line: 0 }, - { uri: other1Uri, line: 1 }, - ); - } - }); - - test('Should support finding references to unknown file', async () => { - const uri1 = workspacePath('doc1.md'); - const doc1 = new InMemoryDocument(uri1, joinLines( - `![img](/images/more/image.png)`, - ``, - `[ref]: /images/more/image.png`, - )); - - const uri2 = workspacePath('sub', 'doc2.md'); - const doc2 = new InMemoryDocument(uri2, joinLines( - `![img](/images/more/image.png)`, - )); - - - const refs = await getReferences(doc1, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc1, doc2])); - assertReferencesEqual(refs!, - { uri: uri1, line: 0 }, - { uri: uri1, line: 2 }, - { uri: uri2, line: 0 }, - ); - }); - - suite('Reference links', () => { - test('Should find reference links within file from link', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, // trigger here - ``, - `[abc]: https://example.com`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - ); - }); - - test('Should find reference links using shorthand', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[ref]`, // trigger 1 - ``, - `[yes][ref]`, // trigger 2 - ``, - `[ref]: /Hello.md` // trigger 3 - )); - - { - const refs = await getReferences(doc, new vscode.Position(0, 2), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - { uri: docUri, line: 4 }, - ); - } - { - const refs = await getReferences(doc, new vscode.Position(2, 7), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - { uri: docUri, line: 4 }, - ); - } - { - const refs = await getReferences(doc, new vscode.Position(4, 2), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - { uri: docUri, line: 4 }, - ); - } - }); - - test('Should find reference links within file from definition', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com`, // trigger here - )); - - const refs = await getReferences(doc, new vscode.Position(2, 3), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - ); - }); - - test('Should not find reference links across files', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com`, - )); - - const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(workspacePath('other.md'), joinLines( - `[link 1][abc]`, - ``, - `[abc]: https://example.com?bad`, - )) - ])); - assertReferencesEqual(refs!, - { uri: docUri, line: 0 }, - { uri: docUri, line: 2 }, - ); - }); - - test('Should not consider checkboxes as reference links', async () => { - const docUri = workspacePath('doc.md'); - const doc = new InMemoryDocument(docUri, joinLines( - `- [x]`, - `- [X]`, - `- [ ]`, - ``, - `[x]: https://example.com` - )); - - const refs = await getReferences(doc, new vscode.Position(0, 4), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(refs?.length!, 0); - }); - }); -}); diff --git a/extensions/markdown-language-features/src/test/rename.test.ts b/extensions/markdown-language-features/src/test/rename.test.ts deleted file mode 100644 index 6560055823d..00000000000 --- a/extensions/markdown-language-features/src/test/rename.test.ts +++ /dev/null @@ -1,692 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; -import { MdReferencesProvider } from '../languageFeatures/references'; -import { MdRenameProvider, MdWorkspaceEdit } from '../languageFeatures/rename'; -import { githubSlugifier } from '../slugify'; -import { noopToken } from '../util/cancellation'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { MdWorkspaceContents } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; -import { assertRangeEqual, joinLines, workspacePath } from './util'; - - -/** - * Get prepare rename info. - */ -function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents): Promise { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier); - return renameProvider.prepareRename(doc, pos, noopToken); -} - -/** - * Get all the edits for the rename. - */ -function getRenameEdits(doc: InMemoryDocument, pos: vscode.Position, newName: string, workspaceContents: MdWorkspaceContents): Promise { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine); - const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); - const renameProvider = new MdRenameProvider(referencesProvider, workspaceContents, githubSlugifier); - return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken); -} - -interface ExpectedTextEdit { - readonly uri: vscode.Uri; - readonly edits: readonly vscode.TextEdit[]; -} - -interface ExpectedFileRename { - readonly originalUri: vscode.Uri; - readonly newUri: vscode.Uri; -} - -function assertEditsEqual(actualEdit: MdWorkspaceEdit, ...expectedEdits: ReadonlyArray) { - // Check file renames - const expectedFileRenames = expectedEdits.filter(expected => 'originalUri' in expected) as ExpectedFileRename[]; - const actualFileRenames = actualEdit.fileRenames ?? []; - assert.strictEqual(actualFileRenames.length, expectedFileRenames.length, `File rename count should match`); - for (let i = 0; i < actualFileRenames.length; ++i) { - const expected = expectedFileRenames[i]; - const actual = actualFileRenames[i]; - assert.strictEqual(actual.from.toString(), expected.originalUri.toString(), `File rename '${i}' should have expected 'from' resource`); - assert.strictEqual(actual.to.toString(), expected.newUri.toString(), `File rename '${i}' should have expected 'to' resource`); - } - - // Check text edits - const actualTextEdits = actualEdit.edit.entries(); - const expectedTextEdits = expectedEdits.filter(expected => 'edits' in expected) as ExpectedTextEdit[]; - assert.strictEqual(actualTextEdits.length, expectedTextEdits.length, `Reference counts should match`); - for (let i = 0; i < actualTextEdits.length; ++i) { - const expected = expectedTextEdits[i]; - const actual = actualTextEdits[i]; - - if ('edits' in expected) { - assert.strictEqual(actual[0].toString(), expected.uri.toString(), `Ref '${i}' has expected document`); - - const actualEditForDoc = actual[1]; - const expectedEditsForDoc = expected.edits; - assert.strictEqual(actualEditForDoc.length, expectedEditsForDoc.length, `Edit counts for '${actual[0]}' should match`); - - for (let g = 0; g < actualEditForDoc.length; ++g) { - assertRangeEqual(actualEditForDoc[g].range, expectedEditsForDoc[g].range, `Edit '${g}' of '${actual[0]}' has expected expected range. Expected range: ${JSON.stringify(actualEditForDoc[g].range)}. Actual range: ${JSON.stringify(expectedEditsForDoc[g].range)}`); - assert.strictEqual(actualEditForDoc[g].newText, expectedEditsForDoc[g].newText, `Edit '${g}' of '${actual[0]}' has expected edits`); - } - } - } -} - -suite('markdown: rename', () => { - - setup(async () => { - // the tests make the assumption that link providers are already registered - await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate(); - }); - - test('Rename on header should not include leading #', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# abc` - )); - - const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5)); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 2, 0, 5), 'New Header') - ] - }); - }); - - test('Rename on header should include leading or trailing #s', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### abc ###` - )); - - const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7)); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 7), 'New Header') - ] - }); - }); - - test('Rename on header should pick up links in doc', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, // rename here - `[text](#a-b-c)`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - ] - }); - }); - - test('Rename on link should use slug for link', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, - `[text](#a-b-c)`, // rename here - )); - - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - ] - }); - }); - - test('Rename on link definition should work', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, - `[text](#a-b-c)`, - `[ref]: #a-b-c`// rename here - )); - - const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - new vscode.TextEdit(new vscode.Range(2, 8, 2, 13), 'new-header'), - ] - }); - }); - - test('Rename on header should pick up links across files', async () => { - const uri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, // rename here - `[text](#a-b-c)`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(otherUri, joinLines( - `[text](#a-b-c)`, // Should not find this - `[text](./doc.md#a-b-c)`, // But should find this - `[text](./doc#a-b-c)`, // And this - )) - ])); - assertEditsEqual(edit!, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - ] - }, { - uri: otherUri, edits: [ - new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), - new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), - ] - }); - }); - - test('Rename on link should pick up links across files', async () => { - const uri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, - `[text](#a-b-c)`, // rename here - )); - - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(otherUri, joinLines( - `[text](#a-b-c)`, // Should not find this - `[text](./doc.md#a-b-c)`, // But should find this - `[text](./doc#a-b-c)`, // And this - )) - ])); - assertEditsEqual(edit!, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - ] - }, { - uri: otherUri, edits: [ - new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), - new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), - ] - }); - }); - - test('Rename on link in other file should pick up all refs', async () => { - const uri = workspacePath('doc.md'); - const otherUri = workspacePath('other.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### A b C`, - `[text](#a-b-c)`, - )); - - const otherDoc = new InMemoryDocument(otherUri, joinLines( - `[text](#a-b-c)`, - `[text](./doc.md#a-b-c)`, - `[text](./doc#a-b-c)` - )); - - const expectedEdits = [ - { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), - ] - }, { - uri: otherUri, edits: [ - new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), - new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), - ] - } - ]; - - { - // Rename on header with file extension - const edit = await getRenameEdits(otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryWorkspaceMarkdownDocuments([ - doc, - otherDoc - ])); - assertEditsEqual(edit!, ...expectedEdits); - } - { - // Rename on header without extension - const edit = await getRenameEdits(otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryWorkspaceMarkdownDocuments([ - doc, - otherDoc - ])); - assertEditsEqual(edit!, ...expectedEdits); - } - }); - - test('Rename on reference should rename references and definition', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text][ref]`, // rename here - `[other][ref]`, - ``, - `[ref]: https://example.com`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 8), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'), - new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'), - ] - }); - }); - - test('Rename on definition should rename references and definitions', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text][ref]`, - `[other][ref]`, - ``, - `[ref]: https://example.com`, // rename here - )); - - const edit = await getRenameEdits(doc, new vscode.Position(3, 3), "new ref", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'), - new vscode.TextEdit(new vscode.Range(1, 8, 1, 11), 'new ref'), - new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'), - ] - }); - }); - - test('Rename on definition entry should rename header and references', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# a B c`, - `[ref text][ref]`, - `[direct](#a-b-c)`, - `[ref]: #a-b-c`, // rename here - )); - - const preparedInfo = await prepareRename(doc, new vscode.Position(3, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(preparedInfo!.placeholder, 'a B c'); - assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13)); - - const edit = await getRenameEdits(doc, new vscode.Position(3, 10), "x Y z", new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'), - new vscode.TextEdit(new vscode.Range(2, 10, 2, 15), 'x-y-z'), - new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'), - ] - }); - }); - - test('Rename should not be supported on link text', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `# Header`, - `[text](#header)`, - )); - - await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryWorkspaceMarkdownDocuments([doc]))); - }); - - test('Path rename should use file path as range', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](./doc.md)`, - `[ref]: ./doc.md`, - )); - - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(info!.placeholder, './doc.md'); - assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15)); - }); - - test('Path rename\'s range should excludes fragment', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](./doc.md#some-header)`, - `[ref]: ./doc.md#some-header`, - )); - - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(info!.placeholder, './doc.md'); - assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15)); - }); - - test('Path rename should update file and all refs', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](./doc.md)`, - `[ref]: ./doc.md`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), './sub/newDoc.md', new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - originalUri: uri, - newUri: workspacePath('sub', 'newDoc.md'), - }, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './sub/newDoc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './sub/newDoc.md'), - ] - }); - }); - - test('Path rename using absolute file path should anchor to workspace root', async () => { - const uri = workspacePath('sub', 'doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](/sub/doc.md)`, - `[ref]: /sub/doc.md`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/newSub/newDoc.md', new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - originalUri: uri, - newUri: workspacePath('newSub', 'newDoc.md'), - }, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/newSub/newDoc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/newSub/newDoc.md'), - ] - }); - }); - - test('Path rename should use un-encoded paths as placeholder', async () => { - const uri = workspacePath('sub', 'doc with spaces.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](/sub/doc%20with%20spaces.md)`, - )); - - const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(info!.placeholder, '/sub/doc with spaces.md'); - }); - - test('Path rename should encode paths', async () => { - const uri = workspacePath('sub', 'doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](/sub/doc.md)`, - `[ref]: /sub/doc.md`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - originalUri: uri, - newUri: workspacePath('NEW sub', 'new DOC.md'), - }, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/NEW%20sub/new%20DOC.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/NEW%20sub/new%20DOC.md'), - ] - }); - }); - - test('Path rename should work with unknown files', async () => { - const uri1 = workspacePath('doc1.md'); - const doc1 = new InMemoryDocument(uri1, joinLines( - `![img](/images/more/image.png)`, - ``, - `[ref]: /images/more/image.png`, - )); - - const uri2 = workspacePath('sub', 'doc2.md'); - const doc2 = new InMemoryDocument(uri2, joinLines( - `![img](/images/more/image.png)`, - )); - - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), '/img/test/new.png', new InMemoryWorkspaceMarkdownDocuments([ - doc1, - doc2 - ])); - assertEditsEqual(edit!, - // Should not have file edits since the files don't exist here - { - uri: uri1, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'), - new vscode.TextEdit(new vscode.Range(2, 7, 2, 29), '/img/test/new.png'), - ] - }, - { - uri: uri2, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'), - ] - }); - }); - - test('Path rename should use .md extension on extension-less link', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[text](/doc#header)`, - `[ref]: /doc#other`, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/new File', new InMemoryWorkspaceMarkdownDocuments([doc])); - assertEditsEqual(edit!, { - originalUri: uri, - newUri: workspacePath('new File.md'), // Rename on disk should use file extension - }, { - uri: uri, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 11), '/new%20File'), // Links should continue to use extension-less paths - new vscode.TextEdit(new vscode.Range(1, 7, 1, 11), '/new%20File'), - ] - }); - }); - - // TODO: fails on windows - test.skip('Path rename should use correctly resolved paths across files', async () => { - const uri1 = workspacePath('sub', 'doc.md'); - const doc1 = new InMemoryDocument(uri1, joinLines( - `[text](./doc.md)`, - `[ref]: ./doc.md`, - )); - - const uri2 = workspacePath('doc2.md'); - const doc2 = new InMemoryDocument(uri2, joinLines( - `[text](./sub/doc.md)`, - `[ref]: ./sub/doc.md`, - )); - - const uri3 = workspacePath('sub2', 'doc3.md'); - const doc3 = new InMemoryDocument(uri3, joinLines( - `[text](../sub/doc.md)`, - `[ref]: ../sub/doc.md`, - )); - - const uri4 = workspacePath('sub2', 'doc4.md'); - const doc4 = new InMemoryDocument(uri4, joinLines( - `[text](/sub/doc.md)`, - `[ref]: /sub/doc.md`, - )); - - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), './new/new-doc.md', new InMemoryWorkspaceMarkdownDocuments([ - doc1, doc2, doc3, doc4, - ])); - assertEditsEqual(edit!, { - originalUri: uri1, - newUri: workspacePath('sub', 'new', 'new-doc.md'), - }, { - uri: uri1, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 15), './new/new-doc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './new/new-doc.md'), - ] - }, { - uri: uri2, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 19), './sub/new/new-doc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 19), './sub/new/new-doc.md'), - ] - }, { - uri: uri3, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 20), '../sub/new/new-doc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 20), '../sub/new/new-doc.md'), - ] - }, { - uri: uri4, edits: [ - new vscode.TextEdit(new vscode.Range(0, 7, 0, 18), '/sub/new/new-doc.md'), - new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/sub/new/new-doc.md'), - ] - }); - }); - - test('Path rename should resolve on links without prefix', async () => { - const uri1 = workspacePath('sub', 'doc.md'); - const doc1 = new InMemoryDocument(uri1, joinLines( - `![text](sub2/doc3.md)`, - )); - - const uri2 = workspacePath('doc2.md'); - const doc2 = new InMemoryDocument(uri2, joinLines( - `![text](sub/sub2/doc3.md)`, - )); - - const uri3 = workspacePath('sub', 'sub2', 'doc3.md'); - const doc3 = new InMemoryDocument(uri3, joinLines()); - - const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), 'sub2/cat.md', new InMemoryWorkspaceMarkdownDocuments([ - doc1, doc2, doc3 - ])); - assertEditsEqual(edit!, { - originalUri: workspacePath('sub', 'sub2', 'doc3.md'), - newUri: workspacePath('sub', 'sub2', 'cat.md'), - }, { - uri: uri1, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 20), 'sub2/cat.md')] - }, { - uri: uri2, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 24), 'sub/sub2/cat.md')] - }); - }); - - test('Rename on link should use header text as placeholder', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `### a B c ###`, - `[text](#a-b-c)`, - )); - - const info = await prepareRename(doc, new vscode.Position(1, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); - assert.strictEqual(info!.placeholder, 'a B c'); - assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13)); - }); - - test('Rename on http uri should work', async () => { - const uri1 = workspacePath('doc.md'); - const uri2 = workspacePath('doc2.md'); - const doc = new InMemoryDocument(uri1, joinLines( - `[1](http://example.com)`, - `[2]: http://example.com`, - ``, - )); - - const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "https://example.com/sub", new InMemoryWorkspaceMarkdownDocuments([ - doc, - new InMemoryDocument(uri2, joinLines( - `[4](http://example.com)`, - )) - ])); - assertEditsEqual(edit!, { - uri: uri1, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'), - new vscode.TextEdit(new vscode.Range(1, 5, 1, 23), 'https://example.com/sub'), - new vscode.TextEdit(new vscode.Range(2, 1, 2, 19), 'https://example.com/sub'), - ] - }, { - uri: uri2, edits: [ - new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'), - ] - }); - }); - - test('Rename on definition path should update all references to path', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[ref text][ref]`, - `[direct](/file)`, - `[ref]: /file`, // rename here - )); - - const workspace = new InMemoryWorkspaceMarkdownDocuments([doc]); - - const preparedInfo = await prepareRename(doc, new vscode.Position(2, 10), workspace); - assert.strictEqual(preparedInfo!.placeholder, '/file'); - assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); - - const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "/newFile", workspace); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/newFile'), - new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/newFile'), - ] - }); - }); - - test('Rename on definition path where file exists should also update file', async () => { - const uri1 = workspacePath('doc.md'); - const doc1 = new InMemoryDocument(uri1, joinLines( - `[ref text][ref]`, - `[direct](/doc2)`, - `[ref]: /doc2`, // rename here - )); - - const uri2 = workspacePath('doc2.md'); - const doc2 = new InMemoryDocument(uri2, joinLines()); - - const workspace = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); - - const preparedInfo = await prepareRename(doc1, new vscode.Position(2, 10), workspace); - assert.strictEqual(preparedInfo!.placeholder, '/doc2'); - assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); - - const edit = await getRenameEdits(doc1, new vscode.Position(2, 10), "/new-doc", workspace); - assertEditsEqual(edit!, { - uri: uri1, edits: [ - new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/new-doc'), - new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/new-doc'), - ] - }, { - originalUri: uri2, - newUri: workspacePath('new-doc.md') - }); - }); - - test('Rename on definition path header should update all references to header', async () => { - const uri = workspacePath('doc.md'); - const doc = new InMemoryDocument(uri, joinLines( - `[ref text][ref]`, - `[direct](/file#header)`, - `[ref]: /file#header`, // rename here - )); - - const workspace = new InMemoryWorkspaceMarkdownDocuments([doc]); - - const preparedInfo = await prepareRename(doc, new vscode.Position(2, 16), workspace); - assert.strictEqual(preparedInfo!.placeholder, 'header'); - assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 13, 2, 19)); - - const edit = await getRenameEdits(doc, new vscode.Position(2, 16), "New Header", workspace); - assertEditsEqual(edit!, { - uri, edits: [ - new vscode.TextEdit(new vscode.Range(1, 15, 1, 21), 'new-header'), - new vscode.TextEdit(new vscode.Range(2, 13, 2, 19), 'new-header'), - ] - }); - }); -}); diff --git a/extensions/markdown-language-features/src/test/smartSelect.test.ts b/extensions/markdown-language-features/src/test/smartSelect.test.ts deleted file mode 100644 index 9d7f60bf481..00000000000 --- a/extensions/markdown-language-features/src/test/smartSelect.test.ts +++ /dev/null @@ -1,726 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import { MdSmartSelect } from '../languageFeatures/smartSelect'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { CURSOR, getCursorPositions, joinLines } from './util'; - -const testFileName = vscode.Uri.file('test.md'); - -suite('markdown.SmartSelect', () => { - test('Smart select single word', async () => { - const ranges = await getSelectionRangesForDocument(`Hel${CURSOR}lo`); - assertNestedLineNumbersEqual(ranges![0], [0, 0]); - }); - - test('Smart select multi-line paragraph', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. `, - `For example, the[node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter]`, - `(https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).` - )); - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select paragraph', async () => { - const ranges = await getSelectionRangesForDocument(`Many of the core components and extensions to ${CURSOR}VS Code live in their own repositories on GitHub. For example, the [node debug adapter](https://github.com/microsoft/vscode-node-debug) and the [mono debug adapter](https://github.com/microsoft/vscode-mono-debug) have their own repositories. For a complete list, please visit the [Related Projects](https://github.com/microsoft/vscode/wiki/Related-Projects) page on our [wiki](https://github.com/microsoft/vscode/wiki).`); - - assertNestedLineNumbersEqual(ranges![0], [0, 0]); - }); - - test('Smart select html block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `

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

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

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

`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 3], [0, 3]); - }); - - test('Smart select fenced code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `~~~`, - `a${CURSOR}`, - `~~~`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- item 1`, - `- ${CURSOR}item 2`, - `- item 3`, - `- item 4`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [0, 3]); - }); - - test('Smart select list with fenced code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- item 1`, - `- ~~~`, - ` ${CURSOR}a`, - ` ~~~`, - `- item 3`, - `- item 4`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 5]); - }); - - test('Smart select multi cursor', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- ${CURSOR}item 1`, - `- ~~~`, - ` a`, - ` ~~~`, - `- ${CURSOR}item 3`, - `- item 4`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 0], [0, 5]); - assertNestedLineNumbersEqual(ranges![1], [4, 4], [0, 5]); - }); - - test('Smart select nested block quotes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `> item 1`, - `> item 2`, - `>> ${CURSOR}item 3`, - `>> item 4`)); - assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [0, 3]); - }); - - test('Smart select multi nested block quotes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `> item 1`, - `>> item 2`, - `>>> ${CURSOR}item 3`, - `>>>> item 4`)); - assertNestedLineNumbersEqual(ranges![0], [2, 2], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select subheader content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `## sub header 1`, - `${CURSOR}content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select subheader line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `## sub header 1${CURSOR}`, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [2, 3], [1, 3], [0, 3]); - }); - - test('Smart select blank line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - `content 1`, - `${CURSOR} `, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [1, 3], [0, 3]); - }); - - test('Smart select line between paragraphs', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `paragraph 1`, - `${CURSOR}`, - `paragraph 2`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 2]); - }); - - test('Smart select empty document', async () => { - const ranges = await getSelectionRangesForDocument(``, [new vscode.Position(0, 0)]); - assert.strictEqual(ranges!.length, 0); - }); - - test('Smart select fenced code block then list then subheader content then subheader then header content then header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - /* 00 */ `# main header 1`, - /* 01 */ `content 1`, - /* 02 */ `## sub header 1`, - /* 03 */ `- item 1`, - /* 04 */ `- ~~~`, - /* 05 */ ` ${CURSOR}a`, - /* 06 */ ` ~~~`, - /* 07 */ `- item 3`, - /* 08 */ `- item 4`, - /* 09 */ ``, - /* 10 */ `more content`, - /* 11 */ `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [4, 6], [3, 8], [3, 10], [2, 10], [1, 10], [0, 10]); - }); - - test('Smart select list with one element without selecting child subheader', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - /* 00 */ `# main header 1`, - /* 01 */ ``, - /* 02 */ `- list ${CURSOR}`, - /* 03 */ ``, - /* 04 */ `## sub header`, - /* 05 */ ``, - /* 06 */ `content 2`, - /* 07 */ `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [2, 2], [1, 3], [1, 6], [0, 6]); - }); - - test('Smart select content under header then subheaders and their content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main ${CURSOR}header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - ``, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [0, 3], [0, 6]); - }); - - test('Smart select last blockquote element under header then subheaders and their content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> ${CURSOR}block`, - ``, - `paragraph`, - `## sub header`, - ``, - `content 2`, - `# main header 2`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [4, 5], [2, 5], [1, 7], [1, 10], [0, 10]); - }); - - test('Smart select content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - `${CURSOR}`, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [11, 11], [9, 12], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select last line content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - ``, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `- ${CURSOR}content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select last line content after content of subheader then subheader then content of main header then main header', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `paragraph`, - `## sub header`, - ``, - ``, - ``, - ``, - `### main header 2`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2${CURSOR}`)); - - assertNestedLineNumbersEqual(ranges![0], [17, 17], [14, 17], [13, 17], [9, 17], [8, 17], [1, 17], [0, 17]); - }); - - test('Smart select fenced code block then list then rest of content', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `- paragraph`, - `- ~~~`, - ` my`, - ` ${CURSOR}code`, - ` goes here`, - ` ~~~`, - `- content`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [9, 11], [8, 12], [8, 12], [7, 17], [1, 17], [0, 17]); - }); - - test('Smart select fenced code block then list then rest of content on fenced line', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `> block`, - `> block`, - `>> block`, - `>> block`, - ``, - `- paragraph`, - `- ~~~${CURSOR}`, - ` my`, - ` code`, - ` goes here`, - ` ~~~`, - `- content`, - `- content 2`, - `- content 2`, - `- content 2`, - `- content 2`)); - - assertNestedLineNumbersEqual(ranges![0], [8, 12], [7, 17], [1, 17], [0, 17]); - }); - - test('Smart select without multiple ranges', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - ``, - `- ${CURSOR}paragraph`, - `- content`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); - }); - - test('Smart select on second level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level 2`, - ` * level 1`, - ` * level ${CURSOR}1`, - `* level 0`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); - }); - - test('Smart select on third level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - ` * level 1`, - ` * level 1`, - `* level 0`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); - }); - - test('Smart select level 2 then level 1', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - `* level 1`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); - }); - - test('Smart select last list item', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `- level 1`, - `- level 2`, - `- level 2`, - `- level ${CURSOR}1`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [0, 3]); - }); - - test('Smart select without multiple ranges', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - ``, - `- ${CURSOR}paragraph`, - `- content`)); - - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [1, 4], [0, 4]); - }); - - test('Smart select on second level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level 2`, - ` * level 1`, - ` * level ${CURSOR}1`, - `* level 0`)); - - assertNestedLineNumbersEqual(ranges![0], [5, 5], [1, 5], [0, 5], [0, 6]); - }); - - test('Smart select on third level of a list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 0`, - ` * level 1`, - ` * level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - ` * level 1`, - ` * level 1`, - `* level 0`)); - assertNestedLineNumbersEqual(ranges![0], [3, 3], [3, 4], [2, 4], [1, 6], [0, 6], [0, 7]); - }); - - test('Smart select level 2 then level 1', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `* level 1`, - ` * level ${CURSOR}2`, - ` * level 2`, - `* level 1`)); - assertNestedLineNumbersEqual(ranges![0], [1, 1], [1, 2], [0, 2], [0, 3]); - }); - - test('Smart select bold', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here **new${CURSOR}item** and here` - )); - assertNestedRangesEqual(ranges![0], [0, 13, 0, 30], [0, 11, 0, 32], [0, 0, 0, 41]); - }); - - test('Smart select link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here [text](https${CURSOR}://google.com) and here` - )); - assertNestedRangesEqual(ranges![0], [0, 18, 0, 46], [0, 17, 0, 47], [0, 11, 0, 47], [0, 0, 0, 56]); - }); - - test('Smart select brackets', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `stuff here [te${CURSOR}xt](https://google.com) and here` - )); - assertNestedRangesEqual(ranges![0], [0, 12, 0, 26], [0, 11, 0, 27], [0, 11, 0, 47], [0, 0, 0, 56]); - }); - - test('Smart select brackets under header in list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [te${CURSOR}xt](https://google.com) and here`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 14, 6, 28], [6, 13, 6, 29], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select link under header in list', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [text](${CURSOR}https://google.com) and here`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 20, 6, 48], [6, 19, 6, 49], [6, 13, 6, 49], [6, 0, 6, 58], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select bold within list where multiple bold elements exists', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `# main header 1`, - ``, - `- list`, - `paragraph`, - `## sub header`, - `- list`, - `- stuff here [text] **${CURSOR}items in here** and **here**`, - `- list` - )); - assertNestedRangesEqual(ranges![0], [6, 22, 6, 45], [6, 20, 6, 47], [6, 0, 6, 60], [5, 0, 7, 6], [4, 0, 7, 6], [1, 0, 7, 6], [0, 0, 7, 6]); - }); - - test('Smart select link in paragraph with multiple links', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `This[extension](https://marketplace.visualstudio.com/items?itemName=meganrogge.template-string-converter) addresses this [requ${CURSOR}est](https://github.com/microsoft/vscode/issues/56704) to convert Javascript/Typescript quotes to backticks when has been entered within a string.` - )); - assertNestedRangesEqual(ranges![0], [0, 123, 0, 140], [0, 122, 0, 141], [0, 122, 0, 191], [0, 0, 0, 283]); - }); - - test('Smart select bold link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `**[extens${CURSOR}ion](https://google.com)**` - )); - assertNestedRangesEqual(ranges![0], [0, 3, 0, 22], [0, 2, 0, 23], [0, 2, 0, 43], [0, 2, 0, 43], [0, 0, 0, 45], [0, 0, 0, 45]); - }); - - test('Smart select inline code block', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `[\`code ${CURSOR} link\`]` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 0, 0, 24]); - }); - - test('Smart select link with inline code block text', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `[\`code ${CURSOR} link\`](http://example.com)` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 22], [0, 1, 0, 23], [0, 1, 0, 23], [0, 0, 0, 24], [0, 0, 0, 44], [0, 0, 0, 44]); - }); - - test('Smart select italic', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*some nice ${CURSOR}text*` - )); - assertNestedRangesEqual(ranges![0], [0, 1, 0, 25], [0, 0, 0, 26], [0, 0, 0, 26]); - }); - - test('Smart select italic link', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*[extens${CURSOR}ion](https://google.com)*` - )); - assertNestedRangesEqual(ranges![0], [0, 2, 0, 21], [0, 1, 0, 22], [0, 1, 0, 42], [0, 1, 0, 42], [0, 0, 0, 43], [0, 0, 0, 43]); - }); - - test('Smart select italic on end', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `*word1 word2 word3${CURSOR}*` - )); - assertNestedRangesEqual(ranges![0], [0, 1, 0, 28], [0, 0, 0, 29], [0, 0, 0, 29]); - }); - - test('Smart select italic then bold', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `outer text **bold words *italic ${CURSOR} words* bold words** outer text` - )); - assertNestedRangesEqual(ranges![0], [0, 25, 0, 48], [0, 24, 0, 49], [0, 13, 0, 60], [0, 11, 0, 62], [0, 0, 0, 73]); - }); - - test('Smart select bold then italic', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `outer text *italic words **bold ${CURSOR} words** italic words* outer text` - )); - assertNestedRangesEqual(ranges![0], [0, 27, 0, 48], [0, 25, 0, 50], [0, 12, 0, 63], [0, 11, 0, 64], [0, 0, 0, 75]); - }); - - test('Third level header from release notes', async () => { - const ranges = await getSelectionRangesForDocument( - joinLines( - `---`, - `Order: 60`, - `TOCTitle: October 2020`, - `PageTitle: Visual Studio Code October 2020`, - `MetaDescription: Learn what is new in the Visual Studio Code October 2020 Release (1.51)`, - `MetaSocialImage: 1_51/release-highlights.png`, - `Date: 2020-11-6`, - `DownloadVersion: 1.51.1`, - `---`, - `# October 2020 (version 1.51)`, - ``, - `**Update 1.51.1**: The update addresses these [issues](https://github.com/microsoft/vscode/issues?q=is%3Aissue+milestone%3A%22October+2020+Recovery%22+is%3Aclosed+).`, - ``, - ``, - ``, - `---`, - ``, - `Welcome to the October 2020 release of Visual Studio Code. As announced in the [October iteration plan](https://github.com/microsoft/vscode/issues/108473), we focused on housekeeping GitHub issues and pull requests as documented in our issue grooming guide.`, - ``, - `We also worked with our partners at GitHub on GitHub Codespaces, which ended up being more involved than originally anticipated. To that end, we'll continue working on housekeeping for part of the November iteration.`, - ``, - `During this housekeeping milestone, we also addressed several feature requests and community [pull requests](#thank-you). Read on to learn about new features and settings.`, - ``, - `## Workbench`, - ``, - `### More prominent pinned tabs`, - ``, - `${CURSOR}Pinned tabs will now always show their pin icon, even while inactive, to make them easier to identify. If an editor is both pinned and contains unsaved changes, the icon reflects both states.`, - ``, - `![Inactive pinned tabs showing pin icons](images/1_51/pinned-tabs.png)` - ) - ); - assertNestedRangesEqual(ranges![0], [27, 0, 27, 201], [26, 0, 29, 70], [25, 0, 29, 70], [24, 0, 29, 70], [23, 0, 29, 70], [10, 0, 29, 70], [9, 0, 29, 70]); - }); - -}); - - -function assertNestedLineNumbersEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number][]) { - const lineage = getLineage(range); - assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); - for (let i = 0; i < lineage.length; i++) { - assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][1], `parent at a depth of ${i}`); - } -} - -function assertNestedRangesEqual(range: vscode.SelectionRange, ...expectedRanges: [number, number, number, number][]) { - const lineage = getLineage(range); - assert.strictEqual(lineage.length, expectedRanges.length, `expected depth: ${expectedRanges.length}, but was ${lineage.length} ${getValues(lineage)}`); - for (let i = 0; i < lineage.length; i++) { - assertLineNumbersEqual(lineage[i], expectedRanges[i][0], expectedRanges[i][2], `parent at a depth of ${i}`); - assert(lineage[i].range.start.character === expectedRanges[i][1], `parent at a depth of ${i} on start char`); - assert(lineage[i].range.end.character === expectedRanges[i][3], `parent at a depth of ${i} on end char`); - } -} - -function getLineage(range: vscode.SelectionRange): vscode.SelectionRange[] { - const result: vscode.SelectionRange[] = []; - let currentRange: vscode.SelectionRange | undefined = range; - while (currentRange) { - result.push(currentRange); - currentRange = currentRange.parent; - } - return result; -} - -function getValues(ranges: vscode.SelectionRange[]): string[] { - return ranges.map(range => { - return range.range.start.line + ' ' + range.range.start.character + ' ' + range.range.end.line + ' ' + range.range.end.character; - }); -} - -function assertLineNumbersEqual(selectionRange: vscode.SelectionRange, startLine: number, endLine: number, message: string) { - assert.strictEqual(selectionRange.range.start.line, startLine, `failed on start line ${message}`); - assert.strictEqual(selectionRange.range.end.line, endLine, `failed on end line ${message}`); -} - -function getSelectionRangesForDocument(contents: string, pos?: vscode.Position[]): Promise { - const doc = new InMemoryDocument(testFileName, contents); - const provider = new MdSmartSelect(createNewMarkdownEngine()); - const positions = pos ? pos : getCursorPositions(contents, doc); - return provider.provideSelectionRanges(doc, positions, new vscode.CancellationTokenSource().token); -} diff --git a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts deleted file mode 100644 index 6efb482ad8f..00000000000 --- a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { TableOfContents } from '../tableOfContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryDocument } from '../util/inMemoryDocument'; - - -const testFileName = vscode.Uri.file('test.md'); - -suite('markdown.TableOfContentsProvider', () => { - test('Lookup should not return anything for empty document', async () => { - const doc = new InMemoryDocument(testFileName, ''); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual(provider.lookup(''), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - }); - - test('Lookup should not return anything for document with no headers', async () => { - const doc = new InMemoryDocument(testFileName, 'a *b*\nc'); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual(provider.lookup(''), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - assert.strictEqual(provider.lookup('a'), undefined); - assert.strictEqual(provider.lookup('b'), undefined); - }); - - test('Lookup should return basic #header', async () => { - const doc = new InMemoryDocument(testFileName, `# a\nx\n# c`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - { - const entry = provider.lookup('a'); - assert.ok(entry); - assert.strictEqual(entry!.line, 0); - } - { - assert.strictEqual(provider.lookup('x'), undefined); - } - { - const entry = provider.lookup('c'); - assert.ok(entry); - assert.strictEqual(entry!.line, 2); - } - }); - - test('Lookups should be case in-sensitive', async () => { - const doc = new InMemoryDocument(testFileName, `# fOo\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual((provider.lookup('fOo'))!.line, 0); - assert.strictEqual((provider.lookup('foo'))!.line, 0); - assert.strictEqual((provider.lookup('FOO'))!.line, 0); - }); - - test('Lookups should ignore leading and trailing white-space, and collapse internal whitespace', async () => { - const doc = new InMemoryDocument(testFileName, `# f o o \n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - assert.strictEqual((provider.lookup(' f o o'))!.line, 0); - assert.strictEqual((provider.lookup(' f o o '))!.line, 0); - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - assert.strictEqual((provider.lookup('f o o'))!.line, 0); - - assert.strictEqual(provider.lookup('f'), undefined); - assert.strictEqual(provider.lookup('foo'), undefined); - assert.strictEqual(provider.lookup('fo o'), undefined); - }); - - test('should handle special characters #44779', async () => { - const doc = new InMemoryDocument(testFileName, `# IndentaçÃŖo\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual((provider.lookup('indentaçÃŖo'))!.line, 0); - }); - - test('should handle special characters 2, #48482', async () => { - const doc = new InMemoryDocument(testFileName, `# ИĐŊŅŅ‚Ņ€ŅƒĐēŅ†Đ¸Ņ - ДĐĩĐģĐ°Đš РаС, ДĐĩĐģĐ°Đš Два\n`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual((provider.lookup('иĐŊŅŅ‚Ņ€ŅƒĐēŅ†Đ¸Ņ---Đ´ĐĩĐģĐ°Đš-Ņ€Đ°Đˇ-Đ´ĐĩĐģĐ°Đš-два'))!.line, 0); - }); - - test('should handle special characters 3, #37079', async () => { - const doc = new InMemoryDocument(testFileName, `## Header 2 -### Header 3 -## ЗаĐŗĐžĐģОвОĐē 2 -### ЗаĐŗĐžĐģОвОĐē 3 -### ЗаĐŗĐžĐģОвОĐē Header 3 -## ЗаĐŗĐžĐģОвОĐē`); - - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - assert.strictEqual((provider.lookup('header-2'))!.line, 0); - assert.strictEqual((provider.lookup('header-3'))!.line, 1); - assert.strictEqual((provider.lookup('ЗаĐŗĐžĐģОвОĐē-2'))!.line, 2); - assert.strictEqual((provider.lookup('ЗаĐŗĐžĐģОвОĐē-3'))!.line, 3); - assert.strictEqual((provider.lookup('ЗаĐŗĐžĐģОвОĐē-header-3'))!.line, 4); - assert.strictEqual((provider.lookup('ЗаĐŗĐžĐģОвОĐē'))!.line, 5); - }); - - test('Lookup should support suffixes for repeated headers', async () => { - const doc = new InMemoryDocument(testFileName, `# a\n# a\n## a`); - const provider = await TableOfContents.create(createNewMarkdownEngine(), doc); - - { - const entry = provider.lookup('a'); - assert.ok(entry); - assert.strictEqual(entry!.line, 0); - } - { - const entry = provider.lookup('a-1'); - assert.ok(entry); - assert.strictEqual(entry!.line, 1); - } - { - const entry = provider.lookup('a-2'); - assert.ok(entry); - assert.strictEqual(entry!.line, 2); - } - }); -}); diff --git a/extensions/markdown-language-features/src/test/util.ts b/extensions/markdown-language-features/src/test/util.ts index 99369ae091f..220e79e2f60 100644 --- a/extensions/markdown-language-features/src/test/util.ts +++ b/extensions/markdown-language-features/src/test/util.ts @@ -5,28 +5,12 @@ import * as assert from 'assert'; import * as os from 'os'; import * as vscode from 'vscode'; -import { InMemoryDocument } from '../util/inMemoryDocument'; +import { DisposableStore } from '../util/dispose'; export const joinLines = (...args: string[]) => args.join(os.platform() === 'win32' ? '\r\n' : '\n'); -export const CURSOR = '$$CURSOR$$'; - -export function getCursorPositions(contents: string, doc: InMemoryDocument): vscode.Position[] { - const positions: vscode.Position[] = []; - let index = 0; - let wordLength = 0; - while (index !== -1) { - index = contents.indexOf(CURSOR, index + wordLength); - if (index !== -1) { - positions.push(doc.positionAt(index)); - } - wordLength = CURSOR.length; - } - return positions; -} - export function workspacePath(...segments: string[]): vscode.Uri { return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments); } @@ -37,3 +21,14 @@ export function assertRangeEqual(expected: vscode.Range, actual: vscode.Range, m assert.strictEqual(expected.end.line, actual.end.line, message); assert.strictEqual(expected.end.character, actual.end.character, message); } + +export function withStore(fn: (this: Mocha.Context, store: DisposableStore) => Promise) { + return async function (this: Mocha.Context): Promise { + const store = new DisposableStore(); + try { + return await fn.call(this, store); + } finally { + store.dispose(); + } + }; +} diff --git a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts deleted file mode 100644 index a059159f613..00000000000 --- a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbolProvider'; -import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbolProvider'; -import { SkinnyTextDocument } from '../workspaceContents'; -import { createNewMarkdownEngine } from './engine'; -import { InMemoryDocument } from '../util/inMemoryDocument'; -import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; - - -const symbolProvider = new MdDocumentSymbolProvider(createNewMarkdownEngine()); - -suite('markdown.WorkspaceSymbolProvider', () => { - test('Should not return anything for empty workspace', async () => { - const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([])); - - assert.deepStrictEqual(await provider.provideWorkspaceSymbols(''), []); - }); - - test('Should return symbols from workspace with one markdown file', async () => { - const testFileName = vscode.Uri.file('test.md'); - - const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(testFileName, `# header1\nabc\n## header2`) - ])); - - const symbols = await provider.provideWorkspaceSymbols(''); - assert.strictEqual(symbols.length, 2); - assert.strictEqual(symbols[0].name, '# header1'); - assert.strictEqual(symbols[1].name, '## header2'); - }); - - test('Should return all content basic workspace', async () => { - const fileNameCount = 10; - const files: SkinnyTextDocument[] = []; - for (let i = 0; i < fileNameCount; ++i) { - const testFileName = vscode.Uri.file(`test${i}.md`); - files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`)); - } - - const provider = new MdWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocuments(files)); - - const symbols = await provider.provideWorkspaceSymbols(''); - assert.strictEqual(symbols.length, fileNameCount * 2); - }); - - test('Should update results when markdown file changes symbols', async () => { - const testFileName = vscode.Uri.file('test.md'); - - const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(testFileName, `# header1`, 1 /* version */) - ]); - - const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider); - - assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1); - - // Update file - workspaceFileProvider.updateDocument(new InMemoryDocument(testFileName, `# new header\nabc\n## header2`, 2 /* version */)); - const newSymbols = await provider.provideWorkspaceSymbols(''); - assert.strictEqual(newSymbols.length, 2); - assert.strictEqual(newSymbols[0].name, '# new header'); - assert.strictEqual(newSymbols[1].name, '## header2'); - }); - - test('Should remove results when file is deleted', async () => { - const testFileName = vscode.Uri.file('test.md'); - - const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(testFileName, `# header1`) - ]); - - const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider); - assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1); - - // delete file - workspaceFileProvider.deleteDocument(testFileName); - const newSymbols = await provider.provideWorkspaceSymbols(''); - assert.strictEqual(newSymbols.length, 0); - }); - - test('Should update results when markdown file is created', async () => { - const testFileName = vscode.Uri.file('test.md'); - - const workspaceFileProvider = new InMemoryWorkspaceMarkdownDocuments([ - new InMemoryDocument(testFileName, `# header1`) - ]); - - const provider = new MdWorkspaceSymbolProvider(symbolProvider, workspaceFileProvider); - assert.strictEqual((await provider.provideWorkspaceSymbols('')).length, 1); - - // Creat file - workspaceFileProvider.createDocument(new InMemoryDocument(vscode.Uri.file('test2.md'), `# new header\nabc\n## header2`)); - const newSymbols = await provider.provideWorkspaceSymbols(''); - assert.strictEqual(newSymbols.length, 3); - }); -}); diff --git a/extensions/markdown-language-features/src/types/textDocument.ts b/extensions/markdown-language-features/src/types/textDocument.ts new file mode 100644 index 00000000000..f45614cc0cc --- /dev/null +++ b/extensions/markdown-language-features/src/types/textDocument.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +/** + * Minimal version of {@link vscode.TextDocument}. + */ +export interface ITextDocument { + readonly uri: vscode.Uri; + readonly version: number; + readonly lineCount: number; + + getText(range?: vscode.Range): string; + positionAt(offset: number): vscode.Position; +} + +export function getLine(doc: ITextDocument, line: number): string { + return doc.getText(new vscode.Range(line, 0, line, Number.MAX_VALUE)).replace(/\r?\n$/, ''); +} diff --git a/extensions/markdown-language-features/src/util/dispose.ts b/extensions/markdown-language-features/src/util/dispose.ts index 175acf7b367..ca78e4a821b 100644 --- a/extensions/markdown-language-features/src/util/dispose.ts +++ b/extensions/markdown-language-features/src/util/dispose.ts @@ -5,13 +5,36 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: vscode.Disposable[]) { - while (disposables.length) { - const item = disposables.pop(); - item?.dispose(); +export class MultiDisposeError extends Error { + constructor( + public readonly errors: any[] + ) { + super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); } } +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + + for (const disposable of disposables) { + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new MultiDisposeError(errors); + } +} + +export interface IDisposable { + dispose(): void; +} + export abstract class Disposable { private _isDisposed = false; @@ -25,7 +48,7 @@ export abstract class Disposable { disposeAll(this._disposables); } - protected _register(value: T): T { + protected _register(value: T): T { if (this._isDisposed) { value.dispose(); } else { @@ -38,3 +61,22 @@ export abstract class Disposable { return this._isDisposed; } } + +export class DisposableStore extends Disposable { + private readonly items = new Set(); + + public override dispose() { + super.dispose(); + disposeAll(this.items); + this.items.clear(); + } + + public add(item: T): T { + if (this.isDisposed) { + console.warn('Adding to disposed store. Item will be leaked'); + } + + this.items.add(item); + return item; + } +} diff --git a/extensions/markdown-language-features/src/util/dom.ts b/extensions/markdown-language-features/src/util/dom.ts new file mode 100644 index 00000000000..0f6c00da9da --- /dev/null +++ b/extensions/markdown-language-features/src/util/dom.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +export function escapeAttribute(value: string | vscode.Uri): string { + return value.toString().replace(/"/g, '"'); +} + +export function getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 64; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/extensions/markdown-language-features/src/util/file.ts b/extensions/markdown-language-features/src/util/file.ts index d61029aaf99..e97ab743929 100644 --- a/extensions/markdown-language-features/src/util/file.ts +++ b/extensions/markdown-language-features/src/util/file.ts @@ -4,7 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as URI from 'vscode-uri'; + +export const markdownFileExtensions = Object.freeze([ + 'md', + 'mkd', + 'mdwn', + 'mdown', + 'markdown', + 'markdn', + 'mdtxt', + 'mdtext', + 'workbook', +]); export function isMarkdownFile(document: vscode.TextDocument) { return document.languageId === 'markdown'; -} \ No newline at end of file +} + +export function looksLikeMarkdownPath(resolvedHrefPath: vscode.Uri) { + return markdownFileExtensions.includes(URI.Utils.extname(resolvedHrefPath).toLowerCase().replace('.', '')); +} diff --git a/extensions/markdown-language-features/src/util/hash.ts b/extensions/markdown-language-features/src/util/hash.ts deleted file mode 100644 index 36365f18fd6..00000000000 --- a/extensions/markdown-language-features/src/util/hash.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -function numberHash(val: number, initialHashVal: number): number { - return (((initialHashVal << 5) - initialHashVal) + val) | 0; // hashVal * 31 + ch, keep as int32 -} - -export function stringHash(s: string) { - let hashVal = numberHash(149417, 0); - for (let i = 0, length = s.length; i < length; i++) { - hashVal = numberHash(s.charCodeAt(i), hashVal); - } - return hashVal; -} diff --git a/extensions/markdown-language-features/src/util/inMemoryDocument.ts b/extensions/markdown-language-features/src/util/inMemoryDocument.ts index 164099d4e10..f626878deff 100644 --- a/extensions/markdown-language-features/src/util/inMemoryDocument.ts +++ b/extensions/markdown-language-features/src/util/inMemoryDocument.ts @@ -5,14 +5,12 @@ import * as vscode from 'vscode'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { SkinnyTextDocument, SkinnyTextLine } from '../workspaceContents'; +import { ITextDocument } from '../types/textDocument'; -export class InMemoryDocument implements SkinnyTextDocument { +export class InMemoryDocument implements ITextDocument { private readonly _doc: TextDocument; - private lines: SkinnyTextLine[] | undefined; - constructor( public readonly uri: vscode.Uri, contents: string, public readonly version = 0, @@ -25,16 +23,6 @@ export class InMemoryDocument implements SkinnyTextDocument { return this._doc.lineCount; } - lineAt(index: any): SkinnyTextLine { - if (!this.lines) { - this.lines = this._doc.getText().split(/\r?\n/).map(text => ({ - text, - get isEmptyOrWhitespace() { return /^\s*$/.test(text); } - })); - } - return this.lines[index]; - } - positionAt(offset: number): vscode.Position { const pos = this._doc.positionAt(offset); return new vscode.Position(pos.line, pos.character); diff --git a/extensions/markdown-language-features/src/util/openDocumentLink.ts b/extensions/markdown-language-features/src/util/openDocumentLink.ts index 376fe548ec4..d117aa15c3e 100644 --- a/extensions/markdown-language-features/src/util/openDocumentLink.ts +++ b/extensions/markdown-language-features/src/util/openDocumentLink.ts @@ -6,8 +6,9 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as uri from 'vscode-uri'; -import { MarkdownEngine } from '../markdownEngine'; -import { TableOfContents } from '../tableOfContents'; +import { MdTableOfContentsProvider } from '../tableOfContents'; +import { ITextDocument } from '../types/textDocument'; +import { IMdWorkspace } from '../workspace'; import { isMarkdownFile } from './file'; export interface OpenDocumentLinkArgs { @@ -37,10 +38,10 @@ export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vsc return vscode.Uri.joinPath(dirnameUri, hrefPath).with({ fragment }); } -export async function openDocumentLink(engine: MarkdownEngine, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise { +export async function openDocumentLink(tocProvider: MdTableOfContentsProvider, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise { const column = getViewColumn(fromResource); - if (await tryNavigateToFragmentInActiveEditor(engine, targetResource)) { + if (await tryNavigateToFragmentInActiveEditor(tocProvider, targetResource)) { return; } @@ -58,7 +59,7 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v try { const stat = await vscode.workspace.fs.stat(dotMdResource); if (stat.type === vscode.FileType.File) { - await tryOpenMdFile(engine, dotMdResource, column); + await tryOpenMdFile(tocProvider, dotMdResource, column); return; } } catch { @@ -69,25 +70,33 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v return vscode.commands.executeCommand('revealInExplorer', targetResource); } - await tryOpenMdFile(engine, targetResource, column); + await tryOpenMdFile(tocProvider, targetResource, column); } -async function tryOpenMdFile(engine: MarkdownEngine, resource: vscode.Uri, column: vscode.ViewColumn): Promise { +async function tryOpenMdFile(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri, column: vscode.ViewColumn): Promise { await vscode.commands.executeCommand('vscode.open', resource.with({ fragment: '' }), column); - return tryNavigateToFragmentInActiveEditor(engine, resource); + return tryNavigateToFragmentInActiveEditor(tocProvider, resource); } -async function tryNavigateToFragmentInActiveEditor(engine: MarkdownEngine, resource: vscode.Uri): Promise { +async function tryNavigateToFragmentInActiveEditor(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri): Promise { + const notebookEditor = vscode.window.activeNotebookEditor; + if (notebookEditor?.notebook.uri.fsPath === resource.fsPath) { + if (await tryRevealLineInNotebook(tocProvider, notebookEditor, resource.fragment)) { + return true; + } + } + const activeEditor = vscode.window.activeTextEditor; if (activeEditor?.document.uri.fsPath === resource.fsPath) { if (isMarkdownFile(activeEditor.document)) { - if (await tryRevealLineUsingTocFragment(engine, activeEditor, resource.fragment)) { + if (await tryRevealLineUsingTocFragment(tocProvider, activeEditor, resource.fragment)) { return true; } } tryRevealLineUsingLineFragment(activeEditor, resource.fragment); return true; } + return false; } @@ -103,8 +112,26 @@ function getViewColumn(resource: vscode.Uri): vscode.ViewColumn { } } -async function tryRevealLineUsingTocFragment(engine: MarkdownEngine, editor: vscode.TextEditor, fragment: string): Promise { - const toc = await TableOfContents.create(engine, editor.document); +async function tryRevealLineInNotebook(tocProvider: MdTableOfContentsProvider, editor: vscode.NotebookEditor, fragment: string): Promise { + const toc = await tocProvider.createForNotebook(editor.notebook); + const entry = toc.lookup(fragment); + if (!entry) { + return false; + } + + const cell = editor.notebook.getCells().find(cell => cell.document.uri.toString() === entry.sectionLocation.uri.toString()); + if (!cell) { + return false; + } + + const range = new vscode.NotebookRange(cell.index, cell.index); + editor.selection = range; + editor.revealRange(range); + return true; +} + +async function tryRevealLineUsingTocFragment(tocProvider: MdTableOfContentsProvider, editor: vscode.TextEditor, fragment: string): Promise { + const toc = await tocProvider.getForDocument(editor.document); const entry = toc.lookup(fragment); if (entry) { const lineStart = new vscode.Range(entry.line, 0, entry.line, 0); @@ -129,9 +156,9 @@ function tryRevealLineUsingLineFragment(editor: vscode.TextEditor, fragment: str return false; } -export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise { +export async function resolveUriToMarkdownFile(workspace: IMdWorkspace, resource: vscode.Uri): Promise { try { - const doc = await tryResolveUriToMarkdownFile(resource); + const doc = await workspace.getOrLoadMarkdownDocument(resource); if (doc) { return doc; } @@ -141,21 +168,8 @@ export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise { - let document: vscode.TextDocument; - try { - document = await vscode.workspace.openTextDocument(resource); - } catch { - return undefined; - } - if (isMarkdownFile(document)) { - return document; - } - return undefined; -} diff --git a/extensions/markdown-language-features/src/util/schemes.ts b/extensions/markdown-language-features/src/util/schemes.ts index 02e5aba6590..3eae0754ad2 100644 --- a/extensions/markdown-language-features/src/util/schemes.ts +++ b/extensions/markdown-language-features/src/util/schemes.ts @@ -3,44 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; - -export const Schemes = { - http: 'http:', - https: 'https:', - file: 'file:', +export const Schemes = Object.freeze({ + file: 'file', untitled: 'untitled', - mailto: 'mailto:', - data: 'data:', - vscode: 'vscode:', - 'vscode-insiders': 'vscode-insiders:', -}; - -const knownSchemes = [ - ...Object.values(Schemes), - `${vscode.env.uriScheme}:` -]; - -export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | undefined { - if (knownSchemes.some(knownScheme => isOfScheme(knownScheme, link))) { - return vscode.Uri.parse(link); - } - - return undefined; -} + mailto: 'mailto', + vscode: 'vscode', + 'vscode-insiders': 'vscode-insiders', + notebookCell: 'vscode-notebook-cell', +}); export function isOfScheme(scheme: string, link: string): boolean { - return link.toLowerCase().startsWith(scheme); + return link.toLowerCase().startsWith(scheme + ':'); } - -export const MarkdownFileExtensions: readonly string[] = [ - '.md', - '.mkd', - '.mdwn', - '.mdown', - '.markdown', - '.markdn', - '.mdtxt', - '.mdtext', - '.workbook', -]; diff --git a/extensions/markdown-language-features/src/util/string.ts b/extensions/markdown-language-features/src/util/string.ts new file mode 100644 index 00000000000..dd9733e9ffd --- /dev/null +++ b/extensions/markdown-language-features/src/util/string.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function isEmptyOrWhitespace(str: string): boolean { + return /^\s*$/.test(str); +} diff --git a/extensions/markdown-language-features/src/util/workspaceCache.ts b/extensions/markdown-language-features/src/util/workspaceCache.ts new file mode 100644 index 00000000000..2569dbee2b4 --- /dev/null +++ b/extensions/markdown-language-features/src/util/workspaceCache.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ITextDocument } from '../types/textDocument'; +import { IMdWorkspace } from '../workspace'; +import { Disposable } from './dispose'; +import { Lazy, lazy } from './lazy'; +import { ResourceMap } from './resourceMap'; + +class LazyResourceMap { + private readonly _map = new ResourceMap>>(); + + public has(resource: vscode.Uri): boolean { + return this._map.has(resource); + } + + public get(resource: vscode.Uri): Promise | undefined { + return this._map.get(resource)?.value; + } + + public set(resource: vscode.Uri, value: Lazy>) { + this._map.set(resource, value); + } + + public delete(resource: vscode.Uri) { + this._map.delete(resource); + } + + public entries(): Promise> { + return Promise.all(Array.from(this._map.entries(), async ([key, entry]) => { + return [key, await entry.value]; + })); + } +} + +/** + * Cache of information per-document in the workspace. + * + * The values are computed lazily and invalidated when the document changes. + */ +export class MdDocumentInfoCache extends Disposable { + + private readonly _cache = new LazyResourceMap(); + private readonly _loadingDocuments = new ResourceMap>(); + + public constructor( + private readonly workspace: IMdWorkspace, + private readonly getValue: (document: ITextDocument) => Promise, + ) { + super(); + + this._register(this.workspace.onDidChangeMarkdownDocument(doc => this.invalidate(doc))); + this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this)); + } + + public async get(resource: vscode.Uri): Promise { + let existing = this._cache.get(resource); + if (existing) { + return existing; + } + + const doc = await this.loadDocument(resource); + if (!doc) { + return undefined; + } + + // Check if we have invalidated + existing = this._cache.get(resource); + if (existing) { + return existing; + } + + return this.resetEntry(doc)?.value; + } + + public async getForDocument(document: ITextDocument): Promise { + const existing = this._cache.get(document.uri); + if (existing) { + return existing; + } + return this.resetEntry(document).value; + } + + private loadDocument(resource: vscode.Uri): Promise { + const existing = this._loadingDocuments.get(resource); + if (existing) { + return existing; + } + + const p = this.workspace.getOrLoadMarkdownDocument(resource); + this._loadingDocuments.set(resource, p); + p.finally(() => { + this._loadingDocuments.delete(resource); + }); + return p; + } + + private resetEntry(document: ITextDocument): Lazy> { + const value = lazy(() => this.getValue(document)); + this._cache.set(document.uri, value); + return value; + } + + private invalidate(document: ITextDocument): void { + if (this._cache.has(document.uri)) { + this.resetEntry(document); + } + } + + private onDidDeleteDocument(resource: vscode.Uri) { + this._cache.delete(resource); + } +} diff --git a/extensions/markdown-language-features/src/workspaceContents.ts b/extensions/markdown-language-features/src/workspace.ts similarity index 61% rename from extensions/markdown-language-features/src/workspaceContents.ts rename to extensions/markdown-language-features/src/workspace.ts index 02477c8c9d7..6d89b79e35d 100644 --- a/extensions/markdown-language-features/src/workspaceContents.ts +++ b/extensions/markdown-language-features/src/workspace.ts @@ -4,48 +4,36 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { ITextDocument } from './types/textDocument'; import { coalesce } from './util/arrays'; import { Disposable } from './util/dispose'; -import { isMarkdownFile } from './util/file'; +import { isMarkdownFile, looksLikeMarkdownPath } from './util/file'; import { InMemoryDocument } from './util/inMemoryDocument'; import { Limiter } from './util/limiter'; - -/** - * Minimal version of {@link vscode.TextLine}. Used for mocking out in testing. - */ -export interface SkinnyTextLine { - readonly text: string; - readonly isEmptyOrWhitespace: boolean; -} - -/** - * Minimal version of {@link vscode.TextDocument}. Used for mocking out in testing. - */ -export interface SkinnyTextDocument { - readonly uri: vscode.Uri; - readonly version: number; - readonly lineCount: number; - - getText(range?: vscode.Range): string; - lineAt(line: number): SkinnyTextLine; - positionAt(offset: number): vscode.Position; -} +import { ResourceMap } from './util/resourceMap'; /** * Provides set of markdown files in the current workspace. */ -export interface MdWorkspaceContents { +export interface IMdWorkspace { /** * Get list of all known markdown files. */ - getAllMarkdownDocuments(): Promise>; + getAllMarkdownDocuments(): Promise>; - getMarkdownDocument(resource: vscode.Uri): Promise; + /** + * Check if a document already exists in the workspace contents. + */ + hasMarkdownDocument(resource: vscode.Uri): boolean; + + getOrLoadMarkdownDocument(resource: vscode.Uri): Promise; pathExists(resource: vscode.Uri): Promise; - readonly onDidChangeMarkdownDocument: vscode.Event; - readonly onDidCreateMarkdownDocument: vscode.Event; + readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]>; + + readonly onDidChangeMarkdownDocument: vscode.Event; + readonly onDidCreateMarkdownDocument: vscode.Event; readonly onDidDeleteMarkdownDocument: vscode.Event; } @@ -54,14 +42,16 @@ export interface MdWorkspaceContents { * * This includes both opened text documents and markdown files in the workspace. */ -export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspaceContents { +export class VsCodeMdWorkspace extends Disposable implements IMdWorkspace { - private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); - private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); + private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); + private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); private _watcher: vscode.FileSystemWatcher | undefined; + private readonly _documentCache = new ResourceMap(); + private readonly utf8Decoder = new TextDecoder('utf-8'); /** @@ -70,19 +60,19 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace * * @returns Array of processed .md files. */ - async getAllMarkdownDocuments(): Promise { + async getAllMarkdownDocuments(): Promise { const maxConcurrent = 20; - const foundFiles = new Set(); - const limiter = new Limiter(maxConcurrent); + const foundFiles = new ResourceMap(); + const limiter = new Limiter(maxConcurrent); // Add files on disk const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**'); const onDiskResults = await Promise.all(resources.map(resource => { return limiter.queue(async () => { - const doc = await this.getMarkdownDocument(resource); + const doc = await this.getOrLoadMarkdownDocument(resource); if (doc) { - foundFiles.add(doc.uri.toString()); + foundFiles.set(resource); } return doc; }); @@ -90,7 +80,7 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace // Add opened files (such as untitled files) const openTextDocumentResults = await Promise.all(vscode.workspace.textDocuments - .filter(doc => !foundFiles.has(doc.uri.toString()) && this.isRelevantMarkdownDocument(doc))); + .filter(doc => !foundFiles.has(doc.uri) && this.isRelevantMarkdownDocument(doc))); return coalesce([...onDiskResults, ...openTextDocumentResults]); } @@ -118,51 +108,80 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md')); this._register(this._watcher.onDidChange(async resource => { - const document = await this.getMarkdownDocument(resource); + this._documentCache.delete(resource); + const document = await this.getOrLoadMarkdownDocument(resource); if (document) { this._onDidChangeMarkdownDocumentEmitter.fire(document); } })); this._register(this._watcher.onDidCreate(async resource => { - const document = await this.getMarkdownDocument(resource); + const document = await this.getOrLoadMarkdownDocument(resource); if (document) { this._onDidCreateMarkdownDocumentEmitter.fire(document); } })); this._register(this._watcher.onDidDelete(resource => { + this._documentCache.delete(resource); this._onDidDeleteMarkdownDocumentEmitter.fire(resource); })); + this._register(vscode.workspace.onDidOpenTextDocument(e => { + this._documentCache.delete(e.uri); + if (this.isRelevantMarkdownDocument(e)) { + this._onDidCreateMarkdownDocumentEmitter.fire(e); + } + })); + this._register(vscode.workspace.onDidChangeTextDocument(e => { if (this.isRelevantMarkdownDocument(e.document)) { this._onDidChangeMarkdownDocumentEmitter.fire(e.document); } })); + + this._register(vscode.workspace.onDidCloseTextDocument(e => { + this._documentCache.delete(e.uri); + })); } private isRelevantMarkdownDocument(doc: vscode.TextDocument) { return isMarkdownFile(doc) && doc.uri.scheme !== 'vscode-bulkeditpreview'; } - public async getMarkdownDocument(resource: vscode.Uri): Promise { + public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise { + const existing = this._documentCache.get(resource); + if (existing) { + return existing; + } + const matchingDocument = vscode.workspace.textDocuments.find((doc) => this.isRelevantMarkdownDocument(doc) && doc.uri.toString() === resource.toString()); if (matchingDocument) { + this._documentCache.set(resource, matchingDocument); return matchingDocument; } + if (!looksLikeMarkdownPath(resource)) { + return undefined; + } + try { const bytes = await vscode.workspace.fs.readFile(resource); // We assume that markdown is in UTF-8 const text = this.utf8Decoder.decode(bytes); - return new InMemoryDocument(resource, text, 0); + const doc = new InMemoryDocument(resource, text, 0); + this._documentCache.set(resource, doc); + return doc; } catch { return undefined; } } + public hasMarkdownDocument(resolvedHrefPath: vscode.Uri): boolean { + return this._documentCache.has(resolvedHrefPath); + } + public async pathExists(target: vscode.Uri): Promise { let targetResourceStat: vscode.FileStat | undefined; try { @@ -172,4 +191,8 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace } return targetResourceStat.type === vscode.FileType.File || targetResourceStat.type === vscode.FileType.Directory; } + + public async readDirectory(resource: vscode.Uri): Promise<[string, vscode.FileType][]> { + return vscode.workspace.fs.readDirectory(resource); + } } diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index 7c1d4a7fca8..75edc8fdacf 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -6,8 +6,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.textEditorDrop.d.ts", - "../../src/vscode-dts/vscode.proposed.dataTransferFiles.d.ts", "../../src/vscode-dts/vscode.proposed.documentPaste.d.ts" ] } diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index eaa76d9abbf..956d9a937f1 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/dompurify@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.1.tgz#2934adcd31c4e6b02676f9c22f9756e5091c04dd" @@ -59,16 +95,37 @@ resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf" integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + dompurify@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" @@ -96,6 +153,13 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + markdown-it-front-matter@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.2.1.tgz#dca49a827bb3cebb0528452c1d87dff276eb28dc" @@ -117,6 +181,13 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + morphdom@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e" @@ -127,22 +198,87 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== + +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== + dependencies: + minimatch "^3.0.4" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" + +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== + dependencies: + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" + vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-languageserver-textdocument@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c" + integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg== + +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== + +vscode-languageserver-types@^3.17.1, vscode-languageserver-types@^3.17.2: + version "3.17.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2" + integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA== + +vscode-markdown-languageservice@^0.0.0-alpha.10: + version "0.0.0-alpha.10" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681" + integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA== + dependencies: + picomatch "^2.3.1" + vscode-languageserver-textdocument "^1.0.5" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" + vscode-uri "^3.0.3" + +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== + +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== vscode-uri@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/extensions/markdown-math/.gitignore b/extensions/markdown-math/.gitignore index 67c177886fb..93d9e664eae 100644 --- a/extensions/markdown-math/.gitignore +++ b/extensions/markdown-math/.gitignore @@ -1 +1,2 @@ notebook-out +languageService diff --git a/extensions/markdown-math/notebook/katex.ts b/extensions/markdown-math/notebook/katex.ts index ee68b15d61b..299ab9373de 100644 --- a/extensions/markdown-math/notebook/katex.ts +++ b/extensions/markdown-math/notebook/katex.ts @@ -8,9 +8,9 @@ import type { RendererContext } from 'vscode-notebook-renderer'; const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css'); export async function activate(ctx: RendererContext) { - const markdownItRenderer = (await ctx.getRenderer('markdownItRenderer')) as undefined | any; + const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as undefined | any; if (!markdownItRenderer) { - throw new Error('Could not load markdownItRenderer'); + throw new Error(`Could not load 'vscode.markdown-it-renderer'`); } // Add katex styles to be copied to shadow dom diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index d3bb21a553a..045da771eb9 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -6,7 +6,7 @@ "icon": "icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.54.0" }, @@ -58,10 +58,10 @@ ], "notebookRenderer": [ { - "id": "markdownItRenderer-katex", + "id": "vscode.markdown-it-katex-extension", "displayName": "Markdown it KaTeX renderer", "entrypoint": { - "extends": "markdownItRenderer", + "extends": "vscode.markdown-it-renderer", "path": "./notebook-out/katex.js" } } diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index 128538a8ad9..d10085c4fc6 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -6,7 +6,7 @@ "icon": "media/icon.png", "version": "1.0.0", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.5.0" }, @@ -77,6 +77,7 @@ "title": "%command.next%", "original": "Next Conflict", "command": "merge-conflict.next", + "enablement": "!isMergeEditor", "icon": "$(arrow-down)" }, { @@ -84,6 +85,7 @@ "title": "%command.previous%", "original": "Previous Conflict", "command": "merge-conflict.previous", + "enablement": "!isMergeEditor", "icon": "$(arrow-up)" }, { @@ -110,12 +112,12 @@ { "command": "merge-conflict.previous", "group": "navigation@1", - "when": "mergeConflictsCount && mergeConflictsCount != 0" + "when": "!isMergeEditor && mergeConflictsCount && mergeConflictsCount != 0" }, { "command": "merge-conflict.next", "group": "navigation@2", - "when": "mergeConflictsCount && mergeConflictsCount != 0" + "when": "!isMergeEditor && mergeConflictsCount && mergeConflictsCount != 0" } ] }, @@ -156,7 +158,7 @@ } }, "dependencies": { - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/merge-conflict/src/documentTracker.ts b/extensions/merge-conflict/src/documentTracker.ts index d6247b20671..f52644ab9df 100644 --- a/extensions/merge-conflict/src/documentTracker.ts +++ b/extensions/merge-conflict/src/documentTracker.ts @@ -69,9 +69,7 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, return cacheItem.delayTask.trigger(() => { const conflicts = this.getConflictsOrEmpty(document, Array.from(cacheItem!.origins)); - if (this.cache) { - this.cache.delete(key!); - } + this.cache?.delete(key!); return conflicts; }); diff --git a/extensions/merge-conflict/src/services.ts b/extensions/merge-conflict/src/services.ts index b8b256aa9f7..9f58daad6f4 100644 --- a/extensions/merge-conflict/src/services.ts +++ b/extensions/merge-conflict/src/services.ts @@ -64,4 +64,3 @@ export default class ServiceWrapper implements vscode.Disposable { this.services = []; } } - diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock index 699f1238a9b..90475ddc7e0 100644 --- a/extensions/merge-conflict/yarn.lock +++ b/extensions/merge-conflict/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 58118fefe5d..b40c7bb17b5 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -36,7 +36,7 @@ } ] }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", "scripts": { @@ -60,8 +60,8 @@ "sha.js": "2.4.11", "stream": "0.0.2", "uuid": "^8.2.0", - "@vscode/extension-telemetry": "0.4.10", - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.6.2", + "vscode-nls": "^5.1.0" }, "repository": { "type": "git", diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index bb22142e584..f32f33bdc96 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -11,7 +11,7 @@ import * as nls from 'vscode-nls'; import { v4 as uuid } from 'uuid'; import fetch, { Response } from 'node-fetch'; import Logger from './logger'; -import { toBase64UrlEncoding } from './utils'; +import { isSupportedEnvironment, toBase64UrlEncoding } from './utils'; import { sha256 } from './env/node/sha256'; import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; import { LoopbackAuthServer } from './authServer'; @@ -184,6 +184,9 @@ export class AzureActiveDirectoryService { if (!modifiedScopes.includes('profile')) { modifiedScopes.push('profile'); } + if (!modifiedScopes.includes('offline_access')) { + modifiedScopes.push('offline_access'); + } modifiedScopes = modifiedScopes.sort(); let modifiedScopesStr = modifiedScopes.join(' '); @@ -319,13 +322,7 @@ export class AzureActiveDirectoryService { }, 5000); } - const token = await this.exchangeCodeForToken(codeToExchange, codeVerifier, scopeData); - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - await this.setToken(token, scopeData); - Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); - const session = await this.convertToSession(token); + const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData); return session; } @@ -355,9 +352,11 @@ export class AzureActiveDirectoryService { const uri = vscode.Uri.parse(`${signInUrl}?${oauthStartQuery.toString()}`); vscode.env.openExternal(uri); + let inputBox: vscode.InputBox | undefined; const timeoutPromise = new Promise((_: (value: vscode.AuthenticationSession) => void, reject) => { const wait = setTimeout(() => { clearTimeout(wait); + inputBox?.dispose(); reject('Login timed out.'); }, 1000 * 60 * 5); }); @@ -369,7 +368,12 @@ export class AzureActiveDirectoryService { // before completing it. let existingPromise = this._codeExchangePromises.get(scopeData.scopeStr); if (!existingPromise) { - existingPromise = this.handleCodeResponse(scopeData); + if (isSupportedEnvironment(callbackUri)) { + existingPromise = this.handleCodeResponse(scopeData); + } else { + inputBox = vscode.window.createInputBox(); + existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData); + } this._codeExchangePromises.set(scopeData.scopeStr, existingPromise); } @@ -659,13 +663,7 @@ export class AzureActiveDirectoryService { throw new Error('No available code verifier'); } - const token = await this.exchangeCodeForToken(code, verifier, scopeData); - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - await this.setToken(token, scopeData); - - const session = await this.convertToSession(token); + const session = await this.exchangeCodeForSession(code, verifier, scopeData); resolve(session); } catch (err) { reject(err); @@ -680,8 +678,33 @@ export class AzureActiveDirectoryService { }); } - private async exchangeCodeForToken(code: string, codeVerifier: string, scopeData: IScopeData): Promise { + private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { + inputBox.ignoreFocusOut = true; + inputBox.title = localize('pasteCodeTitle', 'Microsoft Authentication'); + inputBox.prompt = localize('pasteCodePrompt', 'Provide the authorization code to complete the sign in flow.'); + inputBox.placeholder = localize('pasteCodePlaceholder', 'Paste authorization code here...'); + return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { + inputBox.show(); + inputBox.onDidAccept(async () => { + const code = inputBox.value; + if (code) { + inputBox.dispose(); + const session = await this.exchangeCodeForSession(code, verifier, scopeData); + resolve(session); + } + }); + inputBox.onDidHide(() => { + if (!inputBox.value) { + inputBox.dispose(); + reject('Cancelled'); + } + }); + }); + } + + private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { Logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); + let token: IToken | undefined; try { const postData = querystring.stringify({ grant_type: 'authorization_code', @@ -698,11 +721,18 @@ export class AzureActiveDirectoryService { const json = await this.fetchTokenResponse(endpoint, postData, scopeData); Logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); - return this.convertToTokenSync(json, scopeData); + token = this.convertToTokenSync(json, scopeData); } catch (e) { Logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); throw e; } + + if (token.expiresIn) { + this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); + } + await this.setToken(token, scopeData); + Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); + return await this.convertToSession(token); } private async fetchTokenResponse(endpoint: string, postData: string, scopeData: IScopeData): Promise { diff --git a/extensions/microsoft-authentication/src/utils.ts b/extensions/microsoft-authentication/src/utils.ts index 164f2236221..443bb2dc048 100644 --- a/extensions/microsoft-authentication/src/utils.ts +++ b/extensions/microsoft-authentication/src/utils.ts @@ -2,7 +2,40 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { env, UIKind, Uri } from 'vscode'; export function toBase64UrlEncoding(base64string: string) { return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding } + +const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; +function isLocalhost(uri: Uri): boolean { + if (!/^https?$/i.test(uri.scheme)) { + return false; + } + const host = uri.authority.split(':')[0]; + return LOCALHOST_ADDRESSES.indexOf(host) >= 0; +} + +export function isSupportedEnvironment(uri: Uri): boolean { + if (env.uiKind === UIKind.Desktop) { + return true; + } + // local development (localhost:* or 127.0.0.1:*) + if (isLocalhost(uri)) { + return true; + } + // At this point we should only ever see https + if (uri.scheme !== 'https') { + return false; + } + + return ( + // vscode.dev & insiders.vscode.dev + /(?:^|\.)vscode\.dev$/.test(uri.authority) || + // github.dev & codespaces + /(?:^|\.)github\.dev$/.test(uri.authority) || + // github.dev/codespaces local setup (github.localhost) + /(?:^|\.)github\.localhost$/.test(uri.authority) + ); +} diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index b3ad2657f88..6706f6a4aae 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" @@ -39,10 +75,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" asynckit@^0.4.0: version "0.4.0" @@ -159,10 +198,10 @@ uuid@^8.2.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== webidl-conversions@^3.0.0: version "3.0.1" diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index 15b6751e209..f78839bbeeb 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -17,7 +17,7 @@ "contributes": { "notebookRenderer": [ { - "id": "vscode-builtin-notebook-renderer", + "id": "vscode.builtin-renderer", "entrypoint": "./renderer-out/index.js", "displayName": "VS Code Builtin Notebook Output Renderer", "requiresMessaging": "never", diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 1d6d75aa4f7..4a772de4b5a 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -10,13 +10,21 @@ interface IDisposable { dispose(): void; } +interface HtmlRenderingHook { + /** + * Invoked after the output item has been rendered but before it has been appended to the document. + * + * @return A new `HTMLElement` or `undefined` to continue using the provided element. + */ + postRender(outputItem: OutputItem, element: HTMLElement): HTMLElement | undefined; +} + function clearContainer(container: HTMLElement) { while (container.firstChild) { container.removeChild(container.firstChild); } } - function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable { const blob = new Blob([outputInfo.data()], { type: outputInfo.mime }); const src = URL.createObjectURL(blob); @@ -64,12 +72,17 @@ const domEval = (container: Element) => { } }; -function renderHTML(outputInfo: OutputItem, container: HTMLElement): void { +function renderHTML(outputInfo: OutputItem, container: HTMLElement, hooks: Iterable): void { clearContainer(container); + let element: HTMLElement = document.createElement('div'); const htmlContent = outputInfo.text(); - const element = document.createElement('div'); const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent; element.innerHTML = trustedHtml as string; + + for (const hook of hooks) { + element = hook.postRender(outputInfo, element) ?? element; + } + container.appendChild(element); domEval(element); } @@ -167,6 +180,8 @@ function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: Rendere export const activate: ActivationFunction = (ctx) => { const disposables = new Map(); + const htmlHooks = new Set(); + const latestContext = ctx as (RendererContext & { readonly settings: { readonly lineLimit: number } }); const style = document.createElement('style'); @@ -210,6 +225,7 @@ export const activate: ActivationFunction = (ctx) => { } `; document.body.appendChild(style); + return { renderOutputItem: (outputInfo, element) => { switch (outputInfo.mime) { @@ -220,7 +236,7 @@ export const activate: ActivationFunction = (ctx) => { return; } - renderHTML(outputInfo, element); + renderHTML(outputInfo, element, htmlHooks); } break; case 'application/javascript': @@ -267,8 +283,6 @@ export const activate: ActivationFunction = (ctx) => { default: break; } - - }, disposeOutputItem: (id: string | undefined) => { if (id) { @@ -276,6 +290,14 @@ export const activate: ActivationFunction = (ctx) => { } else { disposables.forEach(d => d.dispose()); } + }, + experimental_registerHtmlRenderingHook: (hook: HtmlRenderingHook): IDisposable => { + htmlHooks.add(hook); + return { + dispose: () => { + htmlHooks.delete(hook); + } + }; } }; }; diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index 49ba465f6c4..36edb961f1f 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -11,7 +11,7 @@ function generateViewMoreElement(outputId: string) { first.textContent = 'Output exceeds the '; const second = document.createElement('a'); second.textContent = 'size limit'; - second.href = `command:workbench.action.openSettings?["notebook.output.textLineLimit"]`; + second.href = `command:workbench.action.openSettings?%5B%22notebook.output.textLineLimit%22%5D`; const third = document.createElement('span'); third.textContent = '. Open the full output data'; const forth = document.createElement('a'); diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 6df1a4835e7..9cc9ac5dedb 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -22,7 +22,7 @@ "jsonc-parser": "^2.2.1", "minimatch": "^3.0.4", "request-light": "^0.5.7", - "vscode-nls": "^5.0.0", + "vscode-nls": "^5.1.0", "which": "^2.0.2", "which-pm": "^2.0.0" }, @@ -298,6 +298,12 @@ "tags": [ "usesOnlineServices" ] + }, + "npm.scriptHover": { + "type": "boolean", + "description": "%config.npm.scriptHover%", + "default": true, + "scope": "window" } } }, diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index b0a4f06c01f..44f1d00f0f4 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -15,6 +15,7 @@ "config.npm.scriptExplorerExclude": "An array of regular expressions that indicate which scripts should be excluded from the NPM Scripts view.", "config.npm.enableRunFromFolder": "Enable running npm scripts contained in a folder from the Explorer context menu.", "config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.", + "config.npm.scriptHover": "Display hover with 'Run' and 'Debug' commands for scripts.", "npm.parseError": "Npm task detection: failed to parse the file {0}", "taskdef.script": "The npm script to customize.", "taskdef.path": "The path to the folder of the package.json file that provides the script. Can be omitted.", diff --git a/extensions/npm/src/npmScriptLens.ts b/extensions/npm/src/npmScriptLens.ts index 067209da334..2834d3e7639 100644 --- a/extensions/npm/src/npmScriptLens.ts +++ b/extensions/npm/src/npmScriptLens.ts @@ -71,7 +71,7 @@ export class NpmScriptLensProvider implements CodeLensProvider, Disposable { return []; } - const title = localize('codelens.debug', '{0} Debug', '$(debug-start)'); + const title = '$(debug-start) ' + localize('codelens.debug', 'Debug'); const cwd = path.dirname(document.uri.fsPath); if (this.lensLocation === 'top') { return [ diff --git a/extensions/npm/src/scriptHover.ts b/extensions/npm/src/scriptHover.ts index b8924bb25df..c96f3330d39 100644 --- a/extensions/npm/src/scriptHover.ts +++ b/extensions/npm/src/scriptHover.ts @@ -33,6 +33,7 @@ export function invalidateHoverScriptsCache(document?: TextDocument) { } export class NpmScriptHoverProvider implements HoverProvider { + private enabled: boolean; constructor(private context: ExtensionContext) { context.subscriptions.push(commands.registerCommand('npm.runScriptFromHover', this.runScriptFromHover, this)); @@ -40,9 +41,21 @@ export class NpmScriptHoverProvider implements HoverProvider { context.subscriptions.push(workspace.onDidChangeTextDocument((e) => { invalidateHoverScriptsCache(e.document); })); + + const isEnabled = () => workspace.getConfiguration('npm').get('scriptHover', true); + this.enabled = isEnabled(); + context.subscriptions.push(workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('npm.scriptHover')) { + this.enabled = isEnabled(); + } + })); } public provideHover(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult { + if (!this.enabled) { + return; + } + let hover: Hover | undefined = undefined; if (!cachedDocument || cachedDocument.fsPath !== document.uri.fsPath) { diff --git a/extensions/npm/yarn.lock b/extensions/npm/yarn.lock index bba9ffdbf39..3479f1c58b6 100644 --- a/extensions/npm/yarn.lock +++ b/extensions/npm/yarn.lock @@ -192,10 +192,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== which-pm@^2.0.0: version "2.0.0" diff --git a/extensions/package.json b/extensions/package.json index 2c89538c8a2..754f2ceebc5 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "4.7.3" + "typescript": "^4.8.1-rc" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/php-language-features/package.json b/extensions/php-language-features/package.json index 8c0d484535b..8782b3d83b4 100644 --- a/extensions/php-language-features/package.json +++ b/extensions/php-language-features/package.json @@ -74,7 +74,7 @@ "watch": "npx gulp watch-extension:php-language-features" }, "dependencies": { - "vscode-nls": "^4.0.0", + "vscode-nls": "^5.1.0", "which": "^2.0.2" }, "devDependencies": { diff --git a/extensions/php-language-features/yarn.lock b/extensions/php-language-features/yarn.lock index 8dec3aadc63..62c8b33e55d 100644 --- a/extensions/php-language-features/yarn.lock +++ b/extensions/php-language-features/yarn.lock @@ -17,10 +17,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== which@^2.0.2: version "2.0.2" diff --git a/extensions/references-view/package.json b/extensions/references-view/package.json index 50d084e2751..4176cda3d1a 100644 --- a/extensions/references-view/package.json +++ b/extensions/references-view/package.json @@ -4,7 +4,7 @@ "description": "%description%", "icon": "media/icon.png", "version": "1.0.0", - "publisher": "ms-vscode", + "publisher": "vscode", "license": "MIT", "engines": { "vscode": "^1.67.0" @@ -407,7 +407,7 @@ "watch": "npx gulp watch-extension:references-view" }, "dependencies": { - "vscode-nls": "^5.0.0" + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/references-view/src/references-view.d.ts b/extensions/references-view/src/references-view.d.ts index a2f2cb72049..cafe77f9cb4 100644 --- a/extensions/references-view/src/references-view.d.ts +++ b/extensions/references-view/src/references-view.d.ts @@ -13,7 +13,7 @@ import * as vscode from 'vscode'; * * ```ts * // get references viewlet API - * const api = await vscode.extensions.getExtension('ms-vscode.references-view').activate(); + * const api = await vscode.extensions.getExtension('vscode.references-view').activate(); * * // instantiate and set input which updates the view * const myInput: SymbolTreeInput = ... diff --git a/extensions/references-view/src/tree.ts b/extensions/references-view/src/tree.ts index df38d11407e..b46db07c9ba 100644 --- a/extensions/references-view/src/tree.ts +++ b/extensions/references-view/src/tree.ts @@ -213,7 +213,7 @@ class TreeDndDelegate implements vscode.TreeDragAndDropController { } } if (urls.length > 0) { - data.set('text/uri-list', new vscode.DataTransferItem(urls.join('\n'))); + data.set('text/uri-list', new vscode.DataTransferItem(urls.join('\r\n'))); } } } diff --git a/extensions/references-view/yarn.lock b/extensions/references-view/yarn.lock index 0f041514ffc..76eaba8dd6f 100644 --- a/extensions/references-view/yarn.lock +++ b/extensions/references-view/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.33.tgz#566713b1b626f781c5c58fe3531307283e00720c" integrity sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA== -vscode-nls@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index a33fa89ce30..a8f24c1a572 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -13,7 +13,7 @@ const fs = require('fs'); const merge = require('merge-options'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const { NLSBundlePlugin } = require('vscode-nls-dev/lib/webpack-bundler'); -const { DefinePlugin } = require('webpack'); +const { DefinePlugin, optimize } = require('webpack'); function withNodeDefaults(/**@type WebpackConfig*/extConfig) { /** @type WebpackConfig */ @@ -70,6 +70,10 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { return merge(defaultConfig, extConfig); } +/** + * + * @param {string} context + */ function nodePlugins(context) { // Need to find the top-most `package.json` file const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; @@ -108,21 +112,32 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi rules: [{ test: /\.ts$/, exclude: /node_modules/, - use: [{ - // configure TypeScript loader: - // * enable sources maps for end-to-end source maps - loader: 'ts-loader', - options: { - compilerOptions: { - 'sourceMap': true, - }, - ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }) - } - }] + use: [ + // TODO: bring this back once vscode-nls-dev supports browser + // { + // // vscode-nls-dev loader: + // // * rewrite nls-calls + // loader: 'vscode-nls-dev/lib/webpack-loader', + // options: { + // base: path.join(extConfig.context, 'src') + // } + // }, + { + // configure TypeScript loader: + // * enable sources maps for end-to-end source maps + loader: 'ts-loader', + options: { + compilerOptions: { + 'sourceMap': true, + }, + ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }) + } + }] }] }, externals: { 'vscode': 'commonjs vscode', // ignored because it doesn't exist, + 'vscode-nls-web-data': 'commonjs vscode-nls-web-data', // ignored because this is injected by the webworker extension host 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing' // ignored because we don't ship this module }, @@ -138,27 +153,40 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi }, // yes, really source maps devtool: 'source-map', - plugins: browserPlugins + plugins: browserPlugins(extConfig.context) }; return merge(defaultConfig, extConfig); } -const browserPlugins = [ - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }), - new DefinePlugin({ - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true') - }) -]; - - - +/** + * + * @param {string} context + */ +function browserPlugins(context) { + // Need to find the top-most `package.json` file + // const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; + // const pkgPath = path.join(__dirname, folderName, 'package.json'); + // const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + // const id = `${pkg.publisher}.${pkg.name}`; + return [ + new optimize.LimitChunkCountPlugin({ + maxChunks: 1 + }), + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }), + new DefinePlugin({ + 'process.platform': JSON.stringify('web'), + 'process.env': JSON.stringify({}), + 'process.env.BROWSER_ENV': JSON.stringify('true') + }), + // TODO: bring this back once vscode-nls-dev supports browser + // new NLSBundlePlugin(id) + ]; +} module.exports = withNodeDefaults; module.exports.node = withNodeDefaults; diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 6bb0131dd30..7fa55ea680c 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -19,6 +19,7 @@ "Shell Script", "shellscript", "bash", + "fish", "sh", "zsh", "ksh", @@ -45,6 +46,7 @@ ".zlogout", ".zshenv", ".zsh-theme", + ".fish", ".ksh", ".csh", ".cshrc", @@ -65,7 +67,7 @@ "bashrc_Apple_Terminal", "zshrc_Apple_Terminal" ], - "firstLine": "^#!.*\\b(bash|zsh|sh|ksh|dtksh|pdksh|mksh|ash|dash|yash|sh|csh|jcsh|tcsh|itcsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "firstLine": "^#!.*\\b(bash|fish|zsh|sh|ksh|dtksh|pdksh|mksh|ash|dash|yash|sh|csh|jcsh|tcsh|itcsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": [ "text/x-shellscript" diff --git a/extensions/simple-browser/.vscodeignore b/extensions/simple-browser/.vscodeignore index ec298ce1768..d1006b25b79 100644 --- a/extensions/simple-browser/.vscodeignore +++ b/extensions/simple-browser/.vscodeignore @@ -11,3 +11,4 @@ cgmanifest.json yarn.lock preview-src/** webpack.config.js +esbuild-preview.js diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 711ffbd81e3..fe3f4de2a5f 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -9,7 +9,7 @@ "icon": "media/icon.png", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { "vscode": "^1.53.0" }, @@ -67,8 +67,8 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.4.10", - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.6.2", + "vscode-nls": "^5.1.0" }, "devDependencies": { "@types/vscode-webview": "^1.57.0", diff --git a/extensions/simple-browser/yarn.lock b/extensions/simple-browser/yarn.lock index af6b2add33e..32215e9059b 100644 --- a/extensions/simple-browser/yarn.lock +++ b/extensions/simple-browser/yarn.lock @@ -2,22 +2,61 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/vscode-webview@^1.57.0": version "1.57.0" resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf" integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" vscode-codicons@^0.0.14: version "0.0.14" resolved "https://registry.yarnpkg.com/vscode-codicons/-/vscode-codicons-0.0.14.tgz#e0d05418e2e195564ff6f6a2199d70415911c18f" integrity sha512-6CEH5KT9ct5WMw7n5dlX7rB8ya4CUI2FSq1Wk36XaW+c5RglFtAanUV0T+gvZVVFhl/WxfjTvFHq06Hz9c1SLA== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index f1d6e04e5ad..b40cb39a6e3 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "microsoft/vscode-mssql", "repositoryUrl": "https://github.com/microsoft/vscode-mssql", - "commitHash": "c98518dd7418ddfb6f35676e14cf12791b0a235d" + "commitHash": "b8b58864526c048002b7c3964bdac8aac3713bd9" } }, "license": "MIT", - "version": "1.10.1" + "version": "1.16.0" } ], "version": 1 diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index ec0e461d9f8..26575ff4549 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-mssql/commit/c98518dd7418ddfb6f35676e14cf12791b0a235d", + "version": "https://github.com/microsoft/vscode-mssql/commit/b8b58864526c048002b7c3964bdac8aac3713bd9", "name": "SQL", "scopeName": "source.sql", "patterns": [ @@ -191,7 +191,7 @@ "name": "keyword.operator.concatenator.sql" }, { - "match": "(?i)\\b(avg|checksum_agg|count|count_big|grouping|grouping_id|max|min|sum|stdev|stdevp|var|varp)\\b", + "match": "(?i)\\b(aggregate|approx_count_distinct|avg|checksum_agg|count|count_big|grouping|grouping_id|max|min|sum|stdev|stdevp|var|varp)\\b", "name": "support.function.aggregate.sql" }, { @@ -202,14 +202,26 @@ "match": "(?i)\\b(cast|convert|parse|try_cast|try_convert|try_parse)\\b", "name": "support.function.conversion.sql" }, + { + "match": "(?i)\\b(collationproperty|tertiary_weights)\\b", + "name": "support.function.collation.sql" + }, + { + "match": "(?i)\\b(asymkey_id|asymkeyproperty|certproperty|cert_id|crypt_gen_random|decryptbyasymkey|decryptbycert|decryptbykey|decryptbykeyautoasymkey|decryptbykeyautocert|decryptbypassphrase|encryptbyasymkey|encryptbycert|encryptbykey|encryptbypassphrase|hashbytes|is_objectsigned|key_guid|key_id|key_name|signbyasymkey|signbycert|symkeyproperty|verifysignedbycert|verifysignedbyasymkey)\\b", + "name": "support.function.cryptographic.sql" + }, { "match": "(?i)\\b(cursor_status)\\b", "name": "support.function.cursor.sql" }, { - "match": "(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|eomonth|switchoffset|todatetimeoffset|isdate)\\b", + "match": "(?i)\\b(sysdatetime|sysdatetimeoffset|sysutcdatetime|current_time(stamp)?|getdate|getutcdate|datename|datepart|day|month|year|datefromparts|datetime2fromparts|datetimefromparts|datetimeoffsetfromparts|smalldatetimefromparts|timefromparts|datediff|dateadd|eomonth|switchoffset|todatetimeoffset|isdate|date_bucket)\\b", "name": "support.function.datetime.sql" }, + { + "match": "(?i)\\b(datalength|ident_current|ident_incr|ident_seed|identity|sql_variant_property)\\b", + "name": "support.function.datatype.sql" + }, { "match": "(?i)\\b(coalesce|nullif)\\b", "name": "support.function.expression.sql" @@ -219,7 +231,11 @@ "name": "support.function.globalvar.sql" }, { - "match": "(?i)\\b(choose|iif)\\b", + "match": "(?i)\\b(json|isjson|json_object|json_array|json_value|json_query|json_modify|json_path_exists)\\b", + "name": "support.function.json.sql" + }, + { + "match": "(?i)\\b(choose|iif|greatest|least)\\b", "name": "support.function.logical.sql" }, { @@ -235,7 +251,7 @@ "name": "support.function.ranking.sql" }, { - "match": "(?i)\\b(opendatasource|openrowset|openquery|openxml)\\b", + "match": "(?i)\\b(generate_series|opendatasource|openjson|openrowset|openquery|openxml|predict|string_split)\\b", "name": "support.function.rowset.sql" }, { diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index aa48d9724fe..3157d39b234 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -7,7 +7,11 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "string meta.image.inline.markdown" + ], "settings": { "foreground": "#6688cc" } diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 8072b0bdd6e..71b5a62a274 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -26,7 +26,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown", ], "settings": { "foreground": "#D4D4D4" diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 7a257515d8c..b8eda7974b7 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -17,7 +17,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#FFFFFF" diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index d2349f31249..d88e6ff90d6 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -25,13 +25,15 @@ "notebook.cellBorderColor": "#E8E8E8", "notebook.selectedCellBackground": "#c8ddf150", "statusBarItem.errorBackground": "#c72e0f", - "list.activeSelectionIconForeground": "#FFF" + "list.activeSelectionIconForeground": "#FFF", + "list.focusAndSelectionOutline": "#90C2F9" }, "tokenColors": [ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#000000ff" 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 bc56665a86c..76cdb8a9753 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -60,7 +60,11 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "string meta.image.inline.markdown" + ], "settings": { "foreground": "#d3af86" } diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 11e7caa3c3e..fccdf7cd600 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -110,7 +110,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#F8F8F2" diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 753116e27d1..30615a91f03 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -9,7 +9,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#333333" diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index f5bd596d10d..c139400dc56 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -69,7 +69,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#F8F8F8" diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index 66e11c16c5e..c55af7ab7fe 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -45,7 +45,8 @@ const nonBuiltInLanguages = { // { fileNames, extensions } const inheritIconFromLanguage = { "jsonc": 'json', "postcss": 'css', - "django-html": 'html' + "django-html": 'html', + "blade": 'php' } const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 00e2991fb96..919b27b7c91 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "8dba1bc311dad1b9bc23c4779149f3bf9baa8cb0" + "commitHash": "2d10473b7575ec00c47eda751ea9caeec6b0b606" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index aeb87b845af..f0e47486995 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 41d2556aaa9..abf5c8a0a7e 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -750,6 +750,14 @@ "fontCharacter": "\\E050", "fontColor": "#cc3e44" }, + "_java_1_light": { + "fontCharacter": "\\E050", + "fontColor": "#498ba7" + }, + "_java_1": { + "fontCharacter": "\\E050", + "fontColor": "#519aba" + }, "_javascript_light": { "fontCharacter": "\\E051", "fontColor": "#b7b73b" @@ -1614,7 +1622,7 @@ "hxp": "_haxe_2", "hxml": "_haxe_3", "jade": "_jade", - "class": "_java", + "class": "_java_1", "classpath": "_java", "js.map": "_javascript", "spec.js": "_javascript_1", @@ -1686,6 +1694,7 @@ "tf": "_terraform", "tf.json": "_terraform", "tfvars": "_terraform", + "tfvars.json": "_terraform", "dtx": "_tex_2", "ins": "_tex_3", "toml": "_config", @@ -1863,6 +1872,7 @@ "clojure": "_clojure", "coffeescript": "_coffee", "jsonc": "_json", + "json": "_json", "c": "_c", "cpp": "_cpp", "cuda-cpp": "_cu", @@ -1872,6 +1882,7 @@ "dockerfile": "_docker", "ignore": "_git", "fsharp": "_f-sharp", + "git-commit": "_git", "go": "_go2", "groovy": "_grails", "handlebars": "_mustache", @@ -1880,7 +1891,6 @@ "java": "_java", "javascriptreact": "_react", "javascript": "_javascript", - "json": "_json", "julia": "_julia", "tex": "_tex_1", "latex": "_tex", @@ -1933,7 +1943,8 @@ "vala": "_vala", "vue": "_vue", "postcss": "_css", - "django-html": "_html_3" + "django-html": "_html_3", + "blade": "_php" }, "light": { "file": "_default_light", @@ -2012,7 +2023,7 @@ "hxp": "_haxe_2_light", "hxml": "_haxe_3_light", "jade": "_jade_light", - "class": "_java_light", + "class": "_java_1_light", "classpath": "_java_light", "js.map": "_javascript_light", "spec.js": "_javascript_1_light", @@ -2084,6 +2095,7 @@ "tf": "_terraform_light", "tf.json": "_terraform_light", "tfvars": "_terraform_light", + "tfvars.json": "_terraform_light", "dtx": "_tex_2_light", "ins": "_tex_3_light", "toml": "_config_light", @@ -2179,6 +2191,7 @@ "clojure": "_clojure_light", "coffeescript": "_coffee_light", "jsonc": "_json_light", + "json": "_json_light", "c": "_c_light", "cpp": "_cpp_light", "cuda-cpp": "_cu_light", @@ -2188,6 +2201,7 @@ "dockerfile": "_docker_light", "ignore": "_git_light", "fsharp": "_f-sharp_light", + "git-commit": "_git_light", "go": "_go2_light", "groovy": "_grails_light", "handlebars": "_mustache_light", @@ -2196,7 +2210,6 @@ "java": "_java_light", "javascriptreact": "_react_light", "javascript": "_javascript_light", - "json": "_json_light", "julia": "_julia_light", "tex": "_tex_1_light", "latex": "_tex_light", @@ -2248,7 +2261,8 @@ "vala": "_vala_light", "vue": "_vue_light", "postcss": "_css_light", - "django-html": "_html_3_light" + "django-html": "_html_3_light", + "blade": "_php_light" }, "fileNames": { "mix": "_hex_light", @@ -2330,5 +2344,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/8dba1bc311dad1b9bc23c4779149f3bf9baa8cb0" + "version": "https://github.com/jesseweed/seti-ui/commit/2d10473b7575ec00c47eda751ea9caeec6b0b606" } \ No newline at end of file diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 2febfd9bca0..e10c6e67403 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -9,7 +9,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#839496" diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 85cdac449d7..e5f8e5b5ee5 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -9,7 +9,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#657B83" @@ -488,6 +489,9 @@ "terminal.ansiBrightMagenta": "#6c71c4", "terminal.ansiBrightCyan": "#93a1a1", "terminal.ansiBrightWhite": "#fdf6e3", + // Set terminal background explicitly, otherwise selection becomes invisible when the + // terminal is in the side bar + "terminal.background": "#FDF6E3", // Interactive Playground "walkThrough.embeddedEditorBackground": "#00000014" }, 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 b37fd6b0083..eef990db889 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 @@ -15,8 +15,8 @@ "editor.foreground": "#ffffff", "editor.selectionBackground": "#003f8e", "minimap.selectionHighlight": "#003f8e", - "editor.lineHighlightBackground": "#00346e", - "editorLineNumber.activeForeground": "#949494", + "editor.lineHighlightBackground": "#00346e", + "editorLineNumber.activeForeground": "#949494", "editorCursor.foreground": "#ffffff", "editorWhitespace.foreground": "#404f7d", "editorWidget.background": "#001c40", @@ -65,7 +65,12 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded", "meta.jsx.children"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "meta.jsx.children", + "string meta.image.inline.markdown" + ], "settings": { //"background": "#002451", "foreground": "#FFFFFF" diff --git a/extensions/typescript-basics/language-configuration.json b/extensions/typescript-basics/language-configuration.json index 07260718b64..739f085339e 100644 --- a/extensions/typescript-basics/language-configuration.json +++ b/extensions/typescript-basics/language-configuration.json @@ -99,6 +99,24 @@ ">" ] ], + "colorizedBracketPairs": [ + [ + "(", + ")" + ], + [ + "[", + "]" + ], + [ + "{", + "}" + ], + [ + "<", + ">" + ] + ], "autoCloseBefore": ";:.,=}])>` \n\t", "folding": { "markers": { diff --git a/extensions/typescript-basics/package.json b/extensions/typescript-basics/package.json index cb6c20d8eed..fef8f0221f0 100644 --- a/extensions/typescript-basics/package.json +++ b/extensions/typescript-basics/package.json @@ -64,6 +64,13 @@ "language": "typescript", "scopeName": "source.ts", "path": "./syntaxes/TypeScript.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.operator.relational", + "storage.type.function.arrow", + "keyword.operator.bitwise.shift", + "meta.brace.angle", + "punctuation.definition.tag" + ], "tokenTypes": { "meta.template.expression": "other", "meta.template.expression string": "string", @@ -78,6 +85,12 @@ "language": "typescriptreact", "scopeName": "source.tsx", "path": "./syntaxes/TypeScriptReact.tmLanguage.json", + "unbalancedBracketScopes": [ + "keyword.operator.relational", + "storage.type.function.arrow", + "keyword.operator.bitwise.shift", + "punctuation.definition.tag" + ], "embeddedLanguages": { "meta.tag.tsx": "jsx-tags", "meta.tag.without-attributes.tsx": "jsx-tags", diff --git a/extensions/typescript-language-features/.eslintrc.json b/extensions/typescript-language-features/.eslintrc.json index ae8cc48c050..f7c6c1b495b 100644 --- a/extensions/typescript-language-features/.eslintrc.json +++ b/extensions/typescript-language-features/.eslintrc.json @@ -1,5 +1,5 @@ { "rules": { - "prefer-const": "error" + "@typescript-eslint/prefer-optional-chain": "warn" } } diff --git a/extensions/typescript-language-features/extension-browser.webpack.config.js b/extensions/typescript-language-features/extension-browser.webpack.config.js index e7ad156bf94..6dbedab0a05 100644 --- a/extensions/typescript-language-features/extension-browser.webpack.config.js +++ b/extensions/typescript-language-features/extension-browser.webpack.config.js @@ -8,6 +8,8 @@ 'use strict'; const CopyPlugin = require('copy-webpack-plugin'); const Terser = require('terser'); +const fs = require('fs'); +const path = require('path'); const defaultConfig = require('../shared.webpack.config'); const withBrowserDefaults = defaultConfig.browser; @@ -35,7 +37,7 @@ module.exports = withBrowserDefaults({ extension: './src/extension.browser.ts', }, plugins: [ - ...browserPlugins, // add plugins, don't replace inherited + ...browserPlugins(__dirname), // add plugins, don't replace inherited // @ts-ignore new CopyPlugin({ @@ -64,9 +66,12 @@ module.exports = withBrowserDefaults({ { from: '../node_modules/typescript/lib/tsserver.js', to: 'typescript/tsserver.web.js', - transform: (content) => { - return Terser.minify(content.toString()).then(output => output.code); + transform: async (content) => { + const dynamicImportCompatPath = path.join(__dirname, '..', 'node_modules', 'typescript', 'lib', 'dynamicImportCompat.js'); + const prefix = fs.existsSync(dynamicImportCompatPath) ? fs.readFileSync(dynamicImportCompatPath) : undefined; + const output = await Terser.minify(content.toString()); + return prefix + '\n' + output.code; }, transformPath: (targetPath) => { return targetPath.replace('tsserver.js', 'tsserver.web.js'); diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 2a7fd5151a1..15a96456934 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -6,7 +6,7 @@ "author": "vscode", "publisher": "vscode", "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ "resolvers", "workspaceTrust" @@ -34,10 +34,11 @@ "Programming Languages" ], "dependencies": { - "@vscode/extension-telemetry": "0.4.10", + "@vscode/extension-telemetry": "0.6.2", "jsonc-parser": "^2.2.1", "semver": "5.5.1", - "vscode-nls": "^5.0.0", + "vscode-nls": "^5.1.0", + "vscode-tas-client": "^0.1.47", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -982,6 +983,22 @@ "markdownDescription": "%typescript.preferences.includePackageJsonAutoImports%", "scope": "window" }, + "typescript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, + "javascript.preferences.autoImportFileExcludePatterns": { + "type": "array", + "items": { + "type": "string" + }, + "markdownDescription": "%typescript.preferences.autoImportFileExcludePatterns%", + "scope": "resource" + }, "javascript.preferences.renameShorthandProperties": { "type": "boolean", "default": true, @@ -1435,7 +1452,7 @@ "background": { "activeOnStart": true, "beginsPattern": { - "regexp": "^\\s*(?:message TS6032:|\\[?\\D*.{1,2}[:.].{1,2}[:.].{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) File change detected\\. Starting incremental compilation\\.\\.\\." + "regexp": "^\\s*(?:message TS6032:|\\[?\\D*.{1,2}[:.].{1,2}[:.].{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) (Starting compilation in watch mode|File change detected\\. Starting incremental compilation)\\.\\.\\." }, "endsPattern": { "regexp": "^\\s*(?:message TS6042:|\\[?\\D*.{1,2}[:.].{1,2}[:.].{1,2}\\D*(├\\D*\\d{1,2}\\D+┤)?(?:\\]| -)) (?:Compilation complete\\.|Found \\d+ errors?\\.) Watching for file changes\\." diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 513bcfbaaa1..03752b1f095 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -136,6 +136,7 @@ "typescript.preferences.includePackageJsonAutoImports.auto": "Search dependencies based on estimated performance impact.", "typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.", "typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.", + "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Requires using TypeScript 4.8 or newer in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts new file mode 100644 index 00000000000..3b24c612c9a --- /dev/null +++ b/extensions/typescript-language-features/src/experimentationService.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import VsCodeTelemetryReporter from '@vscode/extension-telemetry'; +import * as tas from 'vscode-tas-client'; + +interface ExperimentTypes { + // None for now. +} + +export class ExperimentationService implements vscode.Disposable { + private _experimentationServicePromise: Promise; + private _telemetryReporter: ExperimentTelemetryReporter; + + constructor(private readonly _extensionContext: vscode.ExtensionContext) { + this._telemetryReporter = new ExperimentTelemetryReporter(_extensionContext); + this._experimentationServicePromise = this.createExperimentationService(); + } + + public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { + const experimentationService = await this._experimentationServicePromise; + try { + const treatmentVariable = experimentationService.getTreatmentVariableAsync('vscode', name, /*checkCache*/ true) as ExperimentTypes[K]; + return treatmentVariable; + } catch { + return defaultValue; + } + } + + private async createExperimentationService(): Promise { + let targetPopulation: tas.TargetPopulation; + switch (vscode.env.uriScheme) { + case 'vscode': + targetPopulation = tas.TargetPopulation.Public; + case 'vscode-insiders': + targetPopulation = tas.TargetPopulation.Insiders; + case 'vscode-exploration': + targetPopulation = tas.TargetPopulation.Internal; + case 'code-oss': + targetPopulation = tas.TargetPopulation.Team; + default: + targetPopulation = tas.TargetPopulation.Public; + } + + const id = this._extensionContext.extension.id; + const version = this._extensionContext.extension.packageJSON.version || ''; + const experimentationService = tas.getExperimentationService(id, version, targetPopulation, this._telemetryReporter, this._extensionContext.globalState); + await experimentationService.initialFetch; + return experimentationService; + } + + + /** + * @inheritdoc + */ + public dispose() { + this._telemetryReporter.dispose(); + } +} + +export class ExperimentTelemetryReporter + implements tas.IExperimentationTelemetry, vscode.Disposable { + private _sharedProperties: Record = {}; + private _reporter: VsCodeTelemetryReporter; + constructor(ctxt: vscode.ExtensionContext) { + const extension = ctxt.extension; + const packageJSON = extension.packageJSON; + this._reporter = new VsCodeTelemetryReporter( + extension.id, + packageJSON.version || '', + packageJSON.aiKey || ''); + + } + + setSharedProperty(name: string, value: string): void { + this._sharedProperties[name] = value; + } + + postEvent(eventName: string, props: Map): void { + const propsObject = { + ...this._sharedProperties, + ...Object.fromEntries(props), + }; + this._reporter.sendTelemetryEvent(eventName, propsObject); + } + + dispose() { + this._reporter.dispose(); + } +} diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/baseCodeLensProvider.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/baseCodeLensProvider.ts index 890486d7902..214e6b24f7b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/baseCodeLensProvider.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/baseCodeLensProvider.ts @@ -88,7 +88,7 @@ export function getSymbolRange( } // In older versions, we have to calculate this manually. See #23924 - const span = item.spans && item.spans[0]; + const span = item.spans?.[0]; if (!span) { return undefined; } diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index bc0ac10388b..fd3f84963e6 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; import * as vscode from 'vscode'; import type * as Proto from '../protocol'; import { ITypeScriptServiceClient } from '../typescriptService'; @@ -189,6 +190,7 @@ export default class FileConfigurationManager extends Disposable { includeCompletionsWithSnippetText: config.get('suggest.includeCompletionsWithSnippetText', true), includeCompletionsWithClassMemberSnippets: config.get('suggest.classMemberSnippets.enabled', true), includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), + autoImportFileExcludePatterns: this.getAutoImportFileExcludePatternsPreference(preferencesConfig, vscode.workspace.getWorkspaceFolder(document.uri)?.uri), useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, @@ -205,6 +207,18 @@ export default class FileConfigurationManager extends Disposable { default: return this.client.apiVersion.gte(API.v333) ? 'auto' : undefined; } } + + private getAutoImportFileExcludePatternsPreference(config: vscode.WorkspaceConfiguration, workspaceFolder: vscode.Uri | undefined): string[] | undefined { + return workspaceFolder && config.get('autoImportFileExcludePatterns')?.map(p => { + // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578 + const slashNormalized = p.replace(/\\/g, '/'); + const isRelative = /^\.\.?($|\/)/.test(slashNormalized); + return path.isAbsolute(p) ? p : + p.startsWith('*') ? '/' + slashNormalized : + isRelative ? vscode.Uri.joinPath(workspaceFolder, p).fsPath : + '/**/' + slashNormalized; + }); + } } export class InlayHintSettingNames { diff --git a/extensions/typescript-language-features/src/languageFeatures/fixAll.ts b/extensions/typescript-language-features/src/languageFeatures/fixAll.ts index ad8630f70e7..9119b5b66ba 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fixAll.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fixAll.ts @@ -133,7 +133,7 @@ class SourceFixAll extends SourceAction { static readonly kind = vscode.CodeActionKind.SourceFixAll.append('ts'); constructor() { - super(localize('autoFix.label', 'Fix All'), SourceFixAll.kind); + super(localize('autoFix.label', 'Fix all fixable JS/TS issues'), SourceFixAll.kind); } async build(client: ITypeScriptServiceClient, file: string, diagnostics: readonly vscode.Diagnostic[], token: vscode.CancellationToken): Promise { diff --git a/extensions/typescript-language-features/src/languageFeatures/hover.ts b/extensions/typescript-language-features/src/languageFeatures/hover.ts index a717e088a3f..362826513bc 100644 --- a/extensions/typescript-language-features/src/languageFeatures/hover.ts +++ b/extensions/typescript-language-features/src/languageFeatures/hover.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import type * as Proto from '../protocol'; -import { localize } from '../tsServer/versionProvider'; import { ClientCapability, ITypeScriptServiceClient, ServerType } from '../typescriptService'; import { conditionalRegistration, requireSomeCapability } from '../utils/dependentRegistration'; import { DocumentSelector } from '../utils/documentSelector'; @@ -13,6 +13,8 @@ import { markdownDocumentation } from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; +const localize = nls.loadMessageBundle(); + class TypeScriptHoverProvider implements vscode.HoverProvider { diff --git a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts index 1d9c66caa09..8bde8b45df7 100644 --- a/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts +++ b/extensions/typescript-language-features/src/languageFeatures/signatureHelp.ts @@ -64,7 +64,7 @@ class TypeScriptSignatureHelpProvider implements vscode.SignatureHelpProvider { private getActiveParameter(info: Proto.SignatureHelpItems): number { const activeSignature = info.items[info.selectedItemIndex]; - if (activeSignature && activeSignature.isVariadic) { + if (activeSignature?.isVariadic) { return Math.min(info.argumentIndex, activeSignature.parameters.length - 1); } return info.argumentIndex; diff --git a/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts index d020d265995..527a25ef8b2 100644 --- a/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts +++ b/extensions/typescript-language-features/src/languageFeatures/sourceDefinition.ts @@ -6,41 +6,13 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { Command, CommandManager } from '../commands/commandManager'; -import * as Proto from '../protocol'; -import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from '../typescriptService'; +import { ITypeScriptServiceClient } from '../typescriptService'; import API from '../utils/api'; import { isSupportedLanguageMode } from '../utils/languageIds'; import * as typeConverters from '../utils/typeConverters'; const localize = nls.loadMessageBundle(); -namespace ExperimentalProto { - export const enum CommandTypes { - FindSourceDefinition = 'findSourceDefinition' - } - - export interface SourceDefinitionRequestArgs extends Proto.FileLocationRequestArgs { } - - export interface SourceDefinitionRequest extends Proto.Request { - command: CommandTypes.FindSourceDefinition; - arguments: SourceDefinitionRequestArgs; - } - - export interface InlayHintsResponse extends Proto.DefinitionResponse { } - - export interface IExtendedTypeScriptServiceClient { - execute( - command: K, - args: ExtendedTsServerRequests[K][0], - token: vscode.CancellationToken, - config?: ExecConfig - ): Promise>; - } - - export interface ExtendedTsServerRequests { - 'findSourceDefinition': [SourceDefinitionRequestArgs, InlayHintsResponse]; - } -} class SourceDefinitionCommand implements Command { @@ -85,7 +57,7 @@ class SourceDefinitionCommand implements Command { const position = activeEditor.selection.anchor; const args = typeConverters.Position.toFileLocationRequestArgs(openedFiledPath, position); - const response = await (this.client as ExperimentalProto.IExtendedTypeScriptServiceClient).execute('findSourceDefinition', args, token); + const response = await this.client.execute('findSourceDefinition', args, token); if (response.type === 'response' && response.body) { const locations: vscode.Location[] = response.body.map(reference => typeConverters.Location.fromTextSpan(this.client.toResource(reference.file), reference)); diff --git a/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts b/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts index aeaa1bbcaae..c2219b7b4ac 100644 --- a/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts +++ b/extensions/typescript-language-features/src/languageFeatures/tagClosing.ts @@ -53,7 +53,7 @@ class TagClosing extends Disposable { return; } - const activeDocument = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document; + const activeDocument = vscode.window.activeTextEditor?.document; if (document !== activeDocument) { return; } diff --git a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts index ed6af73fcf4..27b41948f75 100644 --- a/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts @@ -11,6 +11,7 @@ import { coalesce } from '../utils/arrays'; import { Delayer, setImmediate } from '../utils/async'; import { nulToken } from '../utils/cancellation'; import { Disposable } from '../utils/dispose'; +import { vscodeNotebookCell } from '../utils/fileSchemes'; import * as languageModeIds from '../utils/languageIds'; import { ResourceMap } from '../utils/resourceMap'; import * as typeConverters from '../utils/typeConverters'; @@ -361,6 +362,98 @@ class GetErrRequest { } } +class TabResourceTracker extends Disposable { + + private readonly _onDidChange = this._register(new vscode.EventEmitter<{ + readonly closed: Iterable; + readonly opened: Iterable; + }>()); + public readonly onDidChange = this._onDidChange.event; + + private readonly _tabResources: ResourceMap<{ readonly tabs: Set }>; + + constructor( + normalizePath: (resource: vscode.Uri) => string | undefined, + config: { + readonly onCaseInsensitiveFileSystem: boolean; + }, + ) { + super(); + + this._tabResources = new ResourceMap<{ readonly tabs: Set }>(normalizePath, config); + + for (const tabGroup of vscode.window.tabGroups.all) { + for (const tab of tabGroup.tabs) { + this.add(tab); + } + } + + this._register(vscode.window.tabGroups.onDidChangeTabs(e => { + const closed = e.closed.flatMap(tab => this.delete(tab)); + const opened = e.opened.flatMap(tab => this.add(tab)); + if (closed.length || opened.length) { + this._onDidChange.fire({ closed, opened }); + } + })); + } + + public has(resource: vscode.Uri): boolean { + if (resource.scheme === vscodeNotebookCell) { + const notebook = vscode.workspace.notebookDocuments.find(doc => + doc.getCells().some(cell => cell.document.uri.toString() === resource.toString())); + + return !!notebook && this.has(notebook.uri); + } + + const entry = this._tabResources.get(resource); + return !!entry && entry.tabs.size > 0; + } + + private add(tab: vscode.Tab): vscode.Uri[] { + const addedResources: vscode.Uri[] = []; + for (const uri of this.getResourcesForTab(tab)) { + const entry = this._tabResources.get(uri); + if (entry) { + entry.tabs.add(tab); + } else { + this._tabResources.set(uri, { tabs: new Set([tab]) }); + addedResources.push(uri); + } + } + return addedResources; + } + + private delete(tab: vscode.Tab): vscode.Uri[] { + const closedResources: vscode.Uri[] = []; + for (const uri of this.getResourcesForTab(tab)) { + const entry = this._tabResources.get(uri); + if (!entry) { + continue; + } + + entry.tabs.delete(tab); + if (entry.tabs.size === 0) { + this._tabResources.delete(uri); + closedResources.push(uri); + } + } + return closedResources; + } + + private getResourcesForTab(tab: vscode.Tab): vscode.Uri[] { + if (tab.input instanceof vscode.TabInputText) { + return [tab.input.uri]; + } else if (tab.input instanceof vscode.TabInputTextDiff) { + return [tab.input.original, tab.input.modified]; + } else if (tab.input instanceof vscode.TabInputNotebook) { + return [tab.input.uri]; + } else { + return []; + } + } +} + + export default class BufferSyncSupport extends Disposable { private readonly client: ITypeScriptServiceClient; @@ -375,6 +468,8 @@ export default class BufferSyncSupport extends Disposable { private listening: boolean = false; private readonly synchronizer: BufferSynchronizer; + private readonly _tabResources: TabResourceTracker; + constructor( client: ITypeScriptServiceClient, modeIds: readonly string[], @@ -391,6 +486,28 @@ export default class BufferSyncSupport extends Disposable { this.pendingDiagnostics = new PendingDiagnostics(pathNormalizer, { onCaseInsensitiveFileSystem }); this.synchronizer = new BufferSynchronizer(client, pathNormalizer, onCaseInsensitiveFileSystem); + this._tabResources = this._register(new TabResourceTracker(pathNormalizer, { onCaseInsensitiveFileSystem })); + this._register(this._tabResources.onDidChange(e => { + if (this.client.configuration.enableProjectDiagnostics) { + return; + } + + for (const closed of e.closed) { + const syncedBuffer = this.syncedBuffers.get(closed); + if (syncedBuffer) { + this.pendingDiagnostics.delete(closed); + this.pendingGetErr?.files.delete(closed); + } + } + + for (const opened of e.opened) { + const syncedBuffer = this.syncedBuffers.get(opened); + if (syncedBuffer) { + this.requestDiagnostic(syncedBuffer); + } + } + })); + this.updateConfiguration(); vscode.workspace.onDidChangeConfiguration(this.updateConfiguration, this, this._disposables); } @@ -494,6 +611,7 @@ export default class BufferSyncSupport extends Disposable { if (!syncedBuffer) { return; } + this.pendingDiagnostics.delete(resource); this.pendingGetErr?.files.delete(resource); this.syncedBuffers.delete(resource); @@ -506,7 +624,7 @@ export default class BufferSyncSupport extends Disposable { public interruptGetErr(f: () => R): R { if (!this.pendingGetErr - || this.client.configuration.enableProjectDiagnostics // `geterr` happens on seperate server so no need to cancel it. + || this.client.configuration.enableProjectDiagnostics // `geterr` happens on separate server so no need to cancel it. ) { return f(); } @@ -628,7 +746,11 @@ export default class BufferSyncSupport extends Disposable { this._validateTypeScript = tsConfig.get('validate.enable', true); } - private shouldValidate(buffer: SyncedBuffer) { + private shouldValidate(buffer: SyncedBuffer): boolean { + if (!this.client.configuration.enableProjectDiagnostics && !this._tabResources.has(buffer.resource)) { // Only validate resources that are showing to the user + return false; + } + switch (buffer.kind) { case BufferKind.JavaScript: return this._validateJavaScript; diff --git a/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts b/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts index 760f82d7b09..ad4f9b9dfe6 100644 --- a/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/versionProvider.electron.ts @@ -6,10 +6,13 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import API from '../utils/api'; import { TypeScriptServiceConfiguration } from '../utils/configuration'; import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver'; -import { ITypeScriptVersionProvider, localize, TypeScriptVersion, TypeScriptVersionSource } from './versionProvider'; +import { ITypeScriptVersionProvider, TypeScriptVersion, TypeScriptVersionSource } from './versionProvider'; + +const localize = nls.loadMessageBundle(); export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider { @@ -28,7 +31,7 @@ export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider public get globalVersion(): TypeScriptVersion | undefined { if (this.configuration?.globalTsdk) { const globals = this.loadVersionsFromSetting(TypeScriptVersionSource.UserSetting, this.configuration.globalTsdk); - if (globals && globals.length) { + if (globals?.length) { return globals[0]; } } @@ -37,7 +40,7 @@ export class DiskTypeScriptVersionProvider implements ITypeScriptVersionProvider public get localVersion(): TypeScriptVersion | undefined { const tsdkVersions = this.localTsdkVersions; - if (tsdkVersions && tsdkVersions.length) { + if (tsdkVersions?.length) { return tsdkVersions[0]; } diff --git a/extensions/typescript-language-features/src/tsServer/versionProvider.ts b/extensions/typescript-language-features/src/tsServer/versionProvider.ts index 43f16c7c19d..89fd07d8c1b 100644 --- a/extensions/typescript-language-features/src/tsServer/versionProvider.ts +++ b/extensions/typescript-language-features/src/tsServer/versionProvider.ts @@ -7,7 +7,7 @@ import * as nls from 'vscode-nls'; import API from '../utils/api'; import { TypeScriptServiceConfiguration } from '../utils/configuration'; -export const localize = nls.loadMessageBundle(); +const localize = nls.loadMessageBundle(); export const enum TypeScriptVersionSource { Bundled = 'bundled', diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 616131b26d8..681cee3a4b5 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -73,6 +73,7 @@ interface StandardTsServerRequests { 'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse]; 'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse]; 'encodedSemanticClassifications-full': [Proto.EncodedSemanticClassificationsRequestArgs, Proto.EncodedSemanticClassificationsResponse]; + 'findSourceDefinition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 0fdecc5c952..a42f5c29f06 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -25,7 +25,7 @@ import * as fileSchemes from './utils/fileSchemes'; import { Logger } from './utils/logger'; import { isWeb } from './utils/platform'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; -import { PluginManager } from './utils/plugins'; +import { PluginManager, TypeScriptServerPlugin } from './utils/plugins'; import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from './utils/telemetry'; import Tracer from './utils/tracer'; import { inferredProjectCompilerOptions, ProjectType } from './utils/tsconfig'; @@ -430,32 +430,29 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); handle.onExit((data: TypeScriptServerExitEvent) => { + const { code, signal } = data; + this.error(`TSServer exited. Code: ${code}. Signal: ${signal}`); + + // In practice, the exit code is an integer with no ties to any identity, + // so it can be classified as SystemMetaData, rather than CallstackOrException. + /* __GDPR__ + "tsserver.exitWithCode" : { + "owner": "mjbvz", + "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "${include}": [ + "${TypeScriptCommonProperties}" + ] + } + */ + this.logTelemetry('tsserver.exitWithCode', { code: code ?? undefined, signal: signal ?? undefined }); + + if (this.token !== mytoken) { // this is coming from an old process return; } - const { code, signal } = data; - - if (code === null || typeof code === 'undefined') { - this.info(`TSServer exited. Signal: ${signal}`); - } else { - // In practice, the exit code is an integer with no ties to any identity, - // so it can be classified as SystemMetaData, rather than CallstackOrException. - this.error(`TSServer exited with code: ${code}. Signal: ${signal}`); - /* __GDPR__ - "tsserver.exitWithCode" : { - "owner": "mjbvz", - "code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "signal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "${include}": [ - "${TypeScriptCommonProperties}" - ] - } - */ - this.logTelemetry('tsserver.exitWithCode', { code, signal: signal ?? undefined }); - } - if (handle.tsServerLogFile) { this.info(`TSServer log file: ${handle.tsServerLogFile}`); } @@ -554,8 +551,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType } // Reconfigure any plugins - for (const [config, pluginName] of this.pluginManager.configurations()) { - this.configurePlugin(config, pluginName); + for (const [pluginName, config] of this.pluginManager.configurations()) { + this.configurePlugin(pluginName, config); } } @@ -587,6 +584,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.numberRestarts++; let startService = true; + const pluginExtensionList = this.pluginManager.plugins.map(plugin => plugin.extension.id).join(', '); const reportIssueItem: vscode.MessageItem = { title: localize('serverDiedReportIssue', 'Report Issue'), }; @@ -599,7 +597,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType startService = false; this.hasServerFatallyCrashedTooManyTimes = true; prompt = vscode.window.showErrorMessage( - localize('serverDiedAfterStart', 'The TypeScript language service died 5 times right after it got started. The service will not be restarted.'), + this.pluginManager.plugins.length + ? localize('serverDiedImmediatelyWithPlugins', "The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}", pluginExtensionList) + : localize('serverDiedImmediately', "The JS/TS language service immediately crashed 5 times. The service will not be restarted."), reportIssueItem); /* __GDPR__ @@ -613,21 +613,30 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.logTelemetry('serviceExited'); } else if (diff < 60 * 1000 * 5 /* 5 Minutes */) { this.lastStart = Date.now(); - prompt = vscode.window.showWarningMessage( - localize('serverDied', 'The TypeScript language service died unexpectedly 5 times in the last 5 Minutes.'), - reportIssueItem); + if (!this._isPromptingAfterCrash) { + prompt = vscode.window.showWarningMessage( + this.pluginManager.plugins.length + ? localize('serverDiedFiveTimesWithPlugins', "The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}", pluginExtensionList) + : localize('serverDiedFiveTimes', "The JS/TS language service crashed 5 times in the last 5 Minutes."), + reportIssueItem); + } } } else if (['vscode-insiders', 'code-oss'].includes(vscode.env.uriScheme)) { // Prompt after a single restart - if (!this._isPromptingAfterCrash && previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError) { - this.numberRestarts = 0; - this._isPromptingAfterCrash = true; + this.numberRestarts = 0; + if (!this._isPromptingAfterCrash) { prompt = vscode.window.showWarningMessage( - localize('serverDiedOnce', 'The TypeScript language service died unexpectedly.'), + this.pluginManager.plugins.length + ? localize('serverDiedOnceWithPlugins', "The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}", pluginExtensionList) + : localize('serverDiedOnce', "The JS/TS language service crashed."), reportIssueItem); } } + if (prompt) { + this._isPromptingAfterCrash = true; + } + prompt?.then(item => { this._isPromptingAfterCrash = false; @@ -647,7 +656,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType }); } else { const args = previousState.type === ServerState.Type.Errored && previousState.error instanceof TypeScriptServerError - ? getReportIssueArgsForError(previousState.error, previousState.tsServerLogFile) + ? getReportIssueArgsForError(previousState.error, previousState.tsServerLogFile, this.pluginManager.plugins) : undefined; vscode.commands.executeCommand('workbench.action.openIssueReporter', args); } @@ -883,7 +892,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.loadingIndicator.reset(); const diagnosticEvent = event as Proto.DiagnosticEvent; - if (diagnosticEvent.body && diagnosticEvent.body.diagnostics) { + if (diagnosticEvent.body?.diagnostics) { this._onDiagnosticsReceived.fire({ kind: getDignosticsKind(event), resource: this.toResource(diagnosticEvent.body.file), @@ -1005,6 +1014,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType function getReportIssueArgsForError( error: TypeScriptServerError, logPath: string | undefined, + globalPlugins: readonly TypeScriptServerPlugin[], ): { extensionId: string; issueTitle: string; issueBody: string } | undefined { if (!error.serverStack || !error.serverMessage) { return undefined; @@ -1023,6 +1033,10 @@ function getReportIssueArgsForError( 3.`, ]; + if (globalPlugins.length) { + sections.push(`**Global TS Server Plugins**\n\n` + globalPlugins.map(plugin => `- \`${plugin.name}\``).join('\n')); + } + if (logPath) { sections.push(`**TS Server Log** @@ -1036,7 +1050,7 @@ The log file may contain personal data, including full paths and source code fro sections.push(`**TS Server Log** -❗ī¸Server logging disabled. To help us fix crashes like this, please enable logging by setting: +❗ī¸ Server logging disabled. To help us fix crashes like this, please enable logging by setting: \`\`\`json "typescript.tsserver.log": "verbose" diff --git a/extensions/typescript-language-features/src/utils/async.ts b/extensions/typescript-language-features/src/utils/async.ts index 75ccf258f28..db92754fd2e 100644 --- a/extensions/typescript-language-features/src/utils/async.ts +++ b/extensions/typescript-language-features/src/utils/async.ts @@ -13,7 +13,7 @@ export class Delayer { public defaultDelay: number; private timeout: any; // Timer - private completionPromise: Promise | null; + private completionPromise: Promise | null; private onSuccess: ((value: T | PromiseLike | undefined) => void) | null; private task: ITask | null; @@ -25,7 +25,7 @@ export class Delayer { this.task = null; } - public trigger(task: ITask, delay: number = this.defaultDelay): Promise { + public trigger(task: ITask, delay: number = this.defaultDelay): Promise { this.task = task; if (delay >= 0) { this.cancelTimeout(); @@ -37,7 +37,7 @@ export class Delayer { }).then(() => { this.completionPromise = null; this.onSuccess = null; - const result = this.task && this.task(); + const result = this.task?.(); this.task = null; return result; }); diff --git a/extensions/typescript-language-features/src/utils/codeAction.ts b/extensions/typescript-language-features/src/utils/codeAction.ts index df345e33517..6a7ad144482 100644 --- a/extensions/typescript-language-features/src/utils/codeAction.ts +++ b/extensions/typescript-language-features/src/utils/codeAction.ts @@ -12,7 +12,7 @@ export function getEditForCodeAction( client: ITypeScriptServiceClient, action: Proto.CodeAction ): vscode.WorkspaceEdit | undefined { - return action.changes && action.changes.length + return action.changes?.length ? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes) : undefined; } @@ -36,7 +36,7 @@ export async function applyCodeActionCommands( commands: ReadonlyArray<{}> | undefined, token: vscode.CancellationToken, ): Promise { - if (commands && commands.length) { + if (commands?.length) { for (const command of commands) { await client.execute('applyCodeActionCommand', { command }, token); } diff --git a/extensions/typescript-language-features/src/utils/configuration.ts b/extensions/typescript-language-features/src/utils/configuration.ts index 2690f743848..efa442a5d0d 100644 --- a/extensions/typescript-language-features/src/utils/configuration.ts +++ b/extensions/typescript-language-features/src/utils/configuration.ts @@ -15,7 +15,7 @@ export enum TsServerLogLevel { export namespace TsServerLogLevel { export function fromString(value: string): TsServerLogLevel { - switch (value && value.toLowerCase()) { + switch (value?.toLowerCase()) { case 'normal': return TsServerLogLevel.Normal; case 'terse': diff --git a/extensions/typescript-language-features/src/utils/logLevelMonitor.ts b/extensions/typescript-language-features/src/utils/logLevelMonitor.ts index 0744a64863e..92a48be4cfd 100644 --- a/extensions/typescript-language-features/src/utils/logLevelMonitor.ts +++ b/extensions/typescript-language-features/src/utils/logLevelMonitor.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { localize } from '../tsServer/versionProvider'; +import * as nls from 'vscode-nls'; import { TsServerLogLevel } from './configuration'; import { Disposable } from './dispose'; +const localize = nls.loadMessageBundle(); + export class LogLevelMonitor extends Disposable { private static readonly logLevelConfigKey = 'typescript.tsserver.log'; diff --git a/extensions/typescript-language-features/src/utils/plugins.ts b/extensions/typescript-language-features/src/utils/plugins.ts index 92d61e3e235..697e5422445 100644 --- a/extensions/typescript-language-features/src/utils/plugins.ts +++ b/extensions/typescript-language-features/src/utils/plugins.ts @@ -8,6 +8,7 @@ import * as arrays from './arrays'; import { Disposable } from './dispose'; export interface TypeScriptServerPlugin { + readonly extension: vscode.Extension; readonly uri: vscode.Uri; readonly name: string; readonly enableForWorkspaceTypeScriptVersions: boolean; @@ -74,6 +75,7 @@ export class PluginManager extends Disposable { const plugins: TypeScriptServerPlugin[] = []; for (const plugin of pack.contributes.typescriptServerPlugins) { plugins.push({ + extension, name: plugin.name, enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions, uri: extension.extensionUri, diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index 2ef271de107..da7ee73cad0 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -55,7 +55,7 @@ export class VSCodeTelemetryReporter implements TelemetryReporter { @memoize private get reporter(): VsCodeTelemetryReporter | null { - if (this.packageInfo && this.packageInfo.aiKey) { + if (this.packageInfo?.aiKey) { this._reporter = new VsCodeTelemetryReporter( this.packageInfo.name, this.packageInfo.version, diff --git a/extensions/typescript-language-features/src/utils/tsconfig.ts b/extensions/typescript-language-features/src/utils/tsconfig.ts index b92a26916d1..baf1d4fd59b 100644 --- a/extensions/typescript-language-features/src/utils/tsconfig.ts +++ b/extensions/typescript-language-features/src/utils/tsconfig.ts @@ -26,7 +26,7 @@ const defaultProjectConfig = Object.freeze module: 'ESNext' as Proto.ModuleKind, moduleResolution: 'Node' as Proto.ModuleResolutionKind, target: 'ES2020' as Proto.ScriptTarget, - jsx: 'preserve' as Proto.JsxEmit, + jsx: 'react' as Proto.JsxEmit, }); export function inferredProjectCompilerOptions( diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 2292b38d296..81f56c011a9 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,6 +2,42 @@ # yarn lockfile v1 +"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" + integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.4" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/1ds-post-js@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" + integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== + dependencies: + "@microsoft/1ds-core-js" "3.2.3" + "@microsoft/applicationinsights-shims" "^2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-core-js@2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" + integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.1" + "@microsoft/dynamicproto-js" "^1.1.6" + +"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" + integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== + +"@microsoft/dynamicproto-js@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" + integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" @@ -12,10 +48,25 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" + integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.3" + "@microsoft/1ds-post-js" "^3.2.3" + +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + +follow-redirects@^1.14.8: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== jsonc-parser@^2.2.1: version "2.3.1" @@ -27,10 +78,24 @@ semver@5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +tas-client@0.1.45: + version "0.1.45" + resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.45.tgz#83bbf73f8458a0f527f9a389f7e1c37f63a64a76" + integrity sha512-IG9UmCpDbDPK23UByQ27rLybkRZYEx2eC9EkieXdwPKKjZPD2zPwfQmyGnZrZet4FUt3yj0ytkwz+liR9Nz/nA== + dependencies: + axios "^0.26.1" + +vscode-nls@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.1.0.tgz#443b301a7465d88c81c0f4e1914f9857f0dce1e4" + integrity sha512-37Ha44QrLFwR2IfSSYdOArzUvOyoWbOYTwQC+wS0NfqKjhW7s0WQ1lMy5oJXgSZy9sAiZS5ifELhbpXodeMR8w== + +vscode-tas-client@^0.1.47: + version "0.1.47" + resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.47.tgz#d66795cbbaa231aba659b6c40d43927d73596375" + integrity sha512-SlEPDi+0gwxor4ANzBtXwqROPQdQkClHeVJgnkvdDF5Xnl407htCsabTPAq4Di8muObORtLchqQS/k1ocaGDEg== + dependencies: + tas-client "0.1.45" vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 7d1b1daca43..54c7463d9a0 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -18,7 +18,6 @@ "fileSearchProvider", "findTextInFiles", "fsChunks", - "inlineCompletions", "notebookCellExecutionState", "notebookContentProvider", "notebookControllerKind", @@ -34,10 +33,10 @@ "scmActionButton", "scmSelectedProvider", "scmValidation", + "snippetWorkspaceEdit", "taskPresentationGroup", "terminalDataWriteEvent", "terminalDimensions", - "terminalNameChangeEvent", "testCoverage", "testObserver", "textSearchProvider", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 874d69541e7..315e94e9be4 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -11,7 +11,10 @@ import { assertNoRpc, closeAllEditors } from '../utils'; suite('vscode API - commands', () => { - teardown(assertNoRpc); + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); test('getCommands', function (done) { @@ -108,19 +111,24 @@ suite('vscode API - commands', () => { }); test('api-command: vscode.open', async function () { - const uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/far.js'); + assert.ok(workspace.workspaceFolders); + assert.ok(workspace.workspaceFolders.length > 0); + const uri = Uri.parse(workspace.workspaceFolders[0].uri.toString() + '/far.js'); await commands.executeCommand('vscode.open', uri); - assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); + assert.strictEqual(window.tabGroups.all.length, 1); assert.strictEqual(window.tabGroups.all[0].activeTab?.group.viewColumn, ViewColumn.One); + assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); await commands.executeCommand('vscode.open', uri, ViewColumn.Two); - assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.Two); + assert.strictEqual(window.tabGroups.all.length, 2); assert.strictEqual(window.tabGroups.all[1].activeTab?.group.viewColumn, ViewColumn.Two); + assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.Two); await commands.executeCommand('vscode.open', uri, ViewColumn.One); - assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); + assert.strictEqual(window.tabGroups.all.length, 2); assert.strictEqual(window.tabGroups.all[0].activeTab?.group.viewColumn, ViewColumn.One); + assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); let e1: Error | undefined = undefined; try { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts index 6880607ca7d..181068b38f6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts @@ -6,22 +6,22 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { disposeAll } from '../utils'; -import { Kernel, saveAllFilesAndCloseAll } from './notebook.test'; +import { asPromise, disposeAll } from '../utils'; +import { Kernel, saveAllFilesAndCloseAll } from './notebook.api.test'; export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor }; async function createInteractiveWindow(kernel: Kernel) { - const { notebookEditor } = (await vscode.commands.executeCommand( + const { notebookEditor, inputUri } = (await vscode.commands.executeCommand( 'interactive.open', // Keep focus on the owning file if there is one { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false }, undefined, - kernel.controller.id, + `vscode.vscode-api-tests/${kernel.controller.id}`, undefined )) as unknown as INativeInteractiveWindow; - return notebookEditor; + return { notebookEditor, inputUri }; } async function addCell(code: string, notebook: vscode.NotebookDocument) { @@ -33,9 +33,11 @@ async function addCell(code: string, notebook: vscode.NotebookDocument) { return notebook.cellAt(notebook.cellCount - 1); } -async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { +async function addCellAndRun(code: string, notebook: vscode.NotebookDocument, i: number) { const cell = await addCell(code, notebook); - await vscode.commands.executeCommand('notebook.execute'); + const event = asPromise(vscode.workspace.onDidChangeNotebookDocument); + await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }); + await event; assert.strictEqual(cell.outputs.length, 1, 'execute failed'); return cell; } @@ -45,11 +47,13 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { const testDisposables: vscode.Disposable[] = []; let defaultKernel: Kernel; + let secondKernel: Kernel; setup(async function () { - // there should be ONE default kernel in this suite defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel', 'interactive'); + secondKernel = new Kernel('secondKernel', 'Notebook Secondary Kernel', 'interactive'); testDisposables.push(defaultKernel.controller); + testDisposables.push(secondKernel.controller); await saveAllFilesAndCloseAll(); }); @@ -59,13 +63,18 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { await saveAllFilesAndCloseAll(); }); - test('Can open an interactive window', async () => { + test('Can open an interactive window and execute from input box', async () => { assert.ok(vscode.workspace.workspaceFolders); - const notebookEditor = await createInteractiveWindow(defaultKernel); + const { notebookEditor, inputUri } = await createInteractiveWindow(defaultKernel); assert.ok(notebookEditor); - // Try adding a cell and running it. - await addCell('print foo', notebookEditor.notebook); + const inputBox = vscode.window.visibleTextEditors.find( + (e) => e.document.uri.path === inputUri.path + ); + await inputBox!.edit((editBuilder) => { + editBuilder.insert(new vscode.Position(0, 0), 'print foo'); + }); + await vscode.commands.executeCommand('interactive.execute', notebookEditor.notebook.uri); assert.strictEqual(notebookEditor.notebook.cellCount, 1); assert.strictEqual(notebookEditor.notebook.cellAt(0).kind, vscode.NotebookCellKind.Code); @@ -73,16 +82,34 @@ async function addCellAndRun(code: string, notebook: vscode.NotebookDocument) { test('Interactive window scrolls after execute', async () => { assert.ok(vscode.workspace.workspaceFolders); - const notebookEditor = await createInteractiveWindow(defaultKernel); + const { notebookEditor } = await createInteractiveWindow(defaultKernel); assert.ok(notebookEditor); // Run and add a bunch of cells - for (let i = 0; i < 20; i++) { - await addCellAndRun(`print ${i}`, notebookEditor.notebook); + for (let i = 0; i < 10; i++) { + await addCellAndRun(`print ${i}`, notebookEditor.notebook, i); } // Verify visible range has the last cell assert.strictEqual(notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end, notebookEditor.notebook.cellCount, `Last cell is not visible`); }); + + test('Interactive window has the correct kernel', async () => { + assert.ok(vscode.workspace.workspaceFolders); + const { notebookEditor } = await createInteractiveWindow(defaultKernel); + assert.ok(notebookEditor); + + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + // Create a new interactive window with a different kernel + const { notebookEditor: notebookEditor2 } = await createInteractiveWindow(secondKernel); + assert.ok(notebookEditor2); + + // Verify the kernel is the secondary one + await addCellAndRun(`print`, notebookEditor2.notebook, 0); + + assert.strictEqual(secondKernel.associatedNotebooks.has(notebookEditor2.notebook.uri.toString()), true, `Secondary kernel was not set as the kernel for the interactive window`); + + }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts new file mode 100644 index 00000000000..233f3ee6177 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts @@ -0,0 +1,358 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import 'mocha'; +import { TextDecoder, TextEncoder } from 'util'; +import * as vscode from 'vscode'; +import { asPromise, assertNoRpc, closeAllEditors, createRandomFile, disposeAll, revertAllDirty, saveAllEditors } from '../utils'; + +async function createRandomNotebookFile() { + return createRandomFile('', undefined, '.vsctestnb'); +} + +async function openRandomNotebookDocument() { + const uri = await createRandomNotebookFile(); + return vscode.workspace.openNotebookDocument(uri); +} + +export async function saveAllFilesAndCloseAll() { + await saveAllEditors(); + await closeAllEditors(); +} + + +function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +export class Kernel { + + readonly controller: vscode.NotebookController; + + readonly associatedNotebooks = new Set(); + + constructor(id: string, label: string, viewType: string = 'notebookCoreTest') { + this.controller = vscode.notebooks.createNotebookController(id, viewType, label); + this.controller.executeHandler = this._execute.bind(this); + this.controller.supportsExecutionOrder = true; + this.controller.supportedLanguages = ['typescript', 'javascript']; + this.controller.onDidChangeSelectedNotebooks(e => { + if (e.selected) { + this.associatedNotebooks.add(e.notebook.uri.toString()); + } else { + this.associatedNotebooks.delete(e.notebook.uri.toString()); + } + }); + } + + protected async _execute(cells: vscode.NotebookCell[]): Promise { + for (const cell of cells) { + await this._runCell(cell); + } + } + + protected async _runCell(cell: vscode.NotebookCell) { + // create a single output with exec order 1 and output is plain/text + // of either the cell itself or (iff empty) the cell's document's uri + const task = this.controller.createNotebookCellExecution(cell); + task.start(Date.now()); + task.executionOrder = 1; + await sleep(10); // Force to be take some time + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain') + ])]); + task.end(true); + } +} + + +function getFocusedCell(editor?: vscode.NotebookEditor) { + return editor ? editor.notebook.cellAt(editor.selections[0].start) : undefined; +} + +const apiTestContentProvider: vscode.NotebookContentProvider = { + openNotebook: async (resource: vscode.Uri): Promise => { + if (/.*empty\-.*\.vsctestnb$/.test(resource.path)) { + return { + metadata: {}, + cells: [] + }; + } + + const dto: vscode.NotebookData = { + metadata: { custom: { testMetadata: false } }, + cells: [ + { + value: 'test', + languageId: 'typescript', + kind: vscode.NotebookCellKind.Code, + outputs: [], + metadata: { custom: { testCellMetadata: 123 } }, + executionSummary: { timing: { startTime: 10, endTime: 20 } } + }, + { + value: 'test2', + languageId: 'typescript', + kind: vscode.NotebookCellKind.Code, + outputs: [ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('Hello World', 'text/plain') + ], + { + testOutputMetadata: true, + ['text/plain']: { testOutputItemMetadata: true } + }) + ], + executionSummary: { executionOrder: 5, success: true }, + metadata: { custom: { testCellMetadata: 456 } } + } + ] + }; + return dto; + }, + saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => { + return { + id: '1', + delete: () => { } + }; + } +}; + +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests', function () { + + const testDisposables: vscode.Disposable[] = []; + const suiteDisposables: vscode.Disposable[] = []; + + suiteTeardown(async function () { + + assertNoRpc(); + + await revertAllDirty(); + await closeAllEditors(); + + disposeAll(suiteDisposables); + suiteDisposables.length = 0; + }); + + suiteSetup(function () { + suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); + }); + + let defaultKernel: Kernel; + + setup(async function () { + // there should be ONE default kernel in this suite + defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); + testDisposables.push(defaultKernel.controller); + await saveAllFilesAndCloseAll(); + }); + + teardown(async function () { + disposeAll(testDisposables); + testDisposables.length = 0; + await saveAllFilesAndCloseAll(); + }); + + test('notebook open', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); + assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); + + const secondCell = editor.notebook.cellAt(1); + assert.strictEqual(secondCell.outputs.length, 1); + assert.deepStrictEqual(secondCell.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } }); + assert.strictEqual(secondCell.outputs[0].items.length, 1); + assert.strictEqual(secondCell.outputs[0].items[0].mime, 'text/plain'); + assert.strictEqual(new TextDecoder().decode(secondCell.outputs[0].items[0].data), 'Hello World'); + assert.strictEqual(secondCell.executionSummary?.executionOrder, 5); + assert.strictEqual(secondCell.executionSummary?.success, true); + }); + + test('multiple tabs: different editors with same document', async function () { + const notebook = await openRandomNotebookDocument(); + const firstNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.One }); + const secondNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.Beside }); + assert.notStrictEqual(firstNotebookEditor, secondNotebookEditor); + assert.strictEqual(firstNotebookEditor?.notebook, secondNotebookEditor?.notebook, 'split notebook editors share the same document'); + }); + + test.skip('#106657. Opening a notebook from markers view is broken ', async function () { + + const document = await openRandomNotebookDocument(); + const [cell] = document.getCells(); + + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // opening a cell-uri opens a notebook editor + await vscode.window.showTextDocument(cell.document, { viewColumn: vscode.ViewColumn.Active }); + // await vscode.commands.executeCommand('vscode.open', cell.document.uri, vscode.ViewColumn.Active); + + assert.strictEqual(!!vscode.window.activeNotebookEditor, true); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); + }); + + test('Cannot open notebook from cell-uri with vscode.open-command', async function () { + + const document = await openRandomNotebookDocument(); + const [cell] = document.getCells(); + + await saveAllFilesAndCloseAll(); + assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + + // BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69) + // removes the fragment if it matches something numeric. For notebooks that's not wanted... + // opening a cell-uri opens a notebook editor + await vscode.commands.executeCommand('vscode.open', cell.document.uri); + + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); + }); + + test('#97830, #97764. Support switch to other editor types', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + const edit = new vscode.WorkspaceEdit(); + const focusedCell = getFocusedCell(editor); + assert.ok(focusedCell); + edit.replace(focusedCell.document.uri, focusedCell.document.lineAt(0).range, 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'var abc = 0;'); + + // no kernel -> no default language + assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); + + await vscode.commands.executeCommand('vscode.openWith', notebook.uri, 'default'); + assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, notebook.uri.path); + }); + + test('#102411 - untitled notebook creation failed', async function () { + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); + assert.notStrictEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); + + await closeAllEditors(); + }); + + test('#115855 onDidSaveNotebookDocument', async function () { + const resource = await createRandomNotebookFile(); + const notebook = await vscode.workspace.openNotebookDocument(resource); + + const notebookEdit = new vscode.NotebookEdit(new vscode.NotebookRange(1, 1), [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'test 2', 'javascript')]); + const edit = new vscode.WorkspaceEdit(); + edit.set(notebook.uri, [notebookEdit]); + await vscode.workspace.applyEdit(edit); + assert.strictEqual(notebook.isDirty, true); + + const saveEvent = asPromise(vscode.workspace.onDidSaveNotebookDocument); + await notebook.save(); + await saveEvent; + + assert.strictEqual(notebook.isDirty, false); + }); +}); + +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('statusbar', () => { + const emitter = new vscode.EventEmitter(); + const onDidCallProvide = emitter.event; + const suiteDisposables: vscode.Disposable[] = []; + suiteTeardown(async function () { + assertNoRpc(); + + await revertAllDirty(); + await closeAllEditors(); + + disposeAll(suiteDisposables); + suiteDisposables.length = 0; + }); + + suiteSetup(() => { + suiteDisposables.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider('notebookCoreTest', { + async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise { + emitter.fire(cell); + return []; + } + })); + + suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); + }); + + test('provideCellStatusBarItems called on metadata change', async function () { + const provideCalled = asPromise(onDidCallProvide); + const notebook = await openRandomNotebookDocument(); + await vscode.window.showNotebookDocument(notebook); + await provideCalled; + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellMetadata(notebook.uri, 0, { inputCollapsed: true }); + await vscode.workspace.applyEdit(edit); + await provideCalled; + }); +}); + +suite('Notebook & LiveShare', function () { + + const suiteDisposables: vscode.Disposable[] = []; + const notebookType = 'vsls-testing'; + + suiteTeardown(() => { + vscode.Disposable.from(...suiteDisposables).dispose(); + }); + + suiteSetup(function () { + + suiteDisposables.push(vscode.workspace.registerNotebookSerializer(notebookType, new class implements vscode.NotebookSerializer { + deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): vscode.NotebookData | Thenable { + const value = new TextDecoder().decode(content); + const cell1 = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang'); + cell1.outputs = [new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(value)])]; + return new vscode.NotebookData([cell1]); + } + serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array | Thenable { + return new TextEncoder().encode(data.cells[0].value); + } + }, {}, { + displayName: 'LS', + filenamePattern: ['*'], + })); + }); + + test('command: vscode.resolveNotebookContentProviders', async function () { + + type Info = { viewType: string; displayName: string; filenamePattern: string[] }; + + const info = await vscode.commands.executeCommand('vscode.resolveNotebookContentProviders'); + assert.strictEqual(Array.isArray(info), true); + + const item = info.find(item => item.viewType === notebookType); + assert.ok(item); + assert.strictEqual(item?.viewType, notebookType); + }); + + test('command: vscode.executeDataToNotebook', async function () { + const value = 'dataToNotebook'; + const data = await vscode.commands.executeCommand('vscode.executeDataToNotebook', notebookType, new TextEncoder().encode(value)); + assert.ok(data instanceof vscode.NotebookData); + assert.strictEqual(data.cells.length, 1); + assert.strictEqual(data.cells[0].value, value); + assert.strictEqual(new TextDecoder().decode(data.cells[0].outputs![0].items[0].data), value); + }); + + test('command: vscode.executeNotebookToData', async function () { + const value = 'notebookToData'; + const notebook = new vscode.NotebookData([new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang')]); + const data = await vscode.commands.executeCommand('vscode.executeNotebookToData', notebookType, notebook); + assert.ok(data instanceof Uint8Array); + assert.deepStrictEqual(new TextDecoder().decode(data), value); + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts index 9fb4248687e..3a28ea9909f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import * as utils from '../utils'; -suite.skip('Notebook Document', function () { +suite('Notebook Document', function () { const simpleContentProvider = new class implements vscode.NotebookSerializer { deserializeNotebook(_data: Uint8Array): vscode.NotebookData | Thenable { @@ -315,6 +315,18 @@ suite.skip('Notebook Document', function () { assert.strictEqual(data.cellChanges[0].cell.index, 0); }); + test('workspace edit API (notebookMetadata)', async function () { + const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const document = await vscode.workspace.openNotebookDocument(uri); + + const edit = new vscode.WorkspaceEdit(); + const metdataEdit = vscode.NotebookEdit.updateNotebookMetadata({ ...document.metadata, custom: { ...(document.metadata.custom || {}), extraNotebookMetadata: true } }); + edit.set(document.uri, [metdataEdit]); + const success = await vscode.workspace.applyEdit(edit); + assert.equal(success, true); + assert.ok(document.metadata.custom.extraNotebookMetadata, `Test metadata not found`); + }); + test('document save API', async function () { const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const notebook = await vscode.workspace.openNotebookDocument(uri); @@ -411,7 +423,7 @@ suite.skip('Notebook Document', function () { assert.strictEqual(document.isDirty, false); }); - test('onDidOpenNotebookDocument - emit event only once when opened in two editors', async function () { + test.skip('onDidOpenNotebookDocument - emit event only once when opened in two editors', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/157222 const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); let counter = 0; testDisposables.push(vscode.workspace.onDidOpenNotebookDocument(nb => { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 12b79163769..e14c48fdd17 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -65,39 +65,6 @@ import * as utils from '../utils'; testDisposables.length = 0; }); - test.skip('showNotebookDocument', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139078 - - const notebookDocumentsFromOnDidOpen = new Set(); - const sub = vscode.workspace.onDidOpenNotebookDocument(e => { - notebookDocumentsFromOnDidOpen.add(e); - }); - - const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); - - const editor = await vscode.window.showNotebookDocument(uri); - assert.strictEqual(uri.toString(), editor.notebook.uri.toString()); - - assert.strictEqual(notebookDocumentsFromOnDidOpen.has(editor.notebook), true); - - const includes = vscode.workspace.notebookDocuments.includes(editor.notebook); - assert.strictEqual(true, includes); - - sub.dispose(); - }); - - // TODO@rebornix deal with getting started - test.skip('notebook editor has viewColumn', async function () { - - const uri1 = await utils.createRandomFile(undefined, undefined, '.nbdtest'); - const editor1 = await vscode.window.showNotebookDocument(uri1); - - assert.strictEqual(editor1.viewColumn, vscode.ViewColumn.One); - - const uri2 = await utils.createRandomFile(undefined, undefined, '.nbdtest'); - const editor2 = await vscode.window.showNotebookDocument(uri2, { viewColumn: vscode.ViewColumn.Beside }); - assert.strictEqual(editor2.viewColumn, vscode.ViewColumn.Two); - }); - // #138683 test('Opening a notebook should fire activeNotebook event changed only once', async function () { const openedEditor = onDidOpenNotebookEditor(); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts new file mode 100644 index 00000000000..923ee4cfc73 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts @@ -0,0 +1,494 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import 'mocha'; +import { TextDecoder } from 'util'; +import * as vscode from 'vscode'; +import { asPromise, assertNoRpc, closeAllEditors, createRandomFile, DeferredPromise, disposeAll, revertAllDirty, saveAllEditors } from '../utils'; + +async function createRandomNotebookFile() { + return createRandomFile('', undefined, '.vsctestnb'); +} + +async function openRandomNotebookDocument() { + const uri = await createRandomNotebookFile(); + return vscode.workspace.openNotebookDocument(uri); +} + +export async function saveAllFilesAndCloseAll() { + await saveAllEditors(); + await closeAllEditors(); +} + +async function withEvent(event: vscode.Event, callback: (e: Promise) => Promise) { + const e = asPromise(event); + await callback(e); +} + + +function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +export class Kernel { + + readonly controller: vscode.NotebookController; + + readonly associatedNotebooks = new Set(); + + constructor(id: string, label: string, viewType: string = 'notebookCoreTest') { + this.controller = vscode.notebooks.createNotebookController(id, viewType, label); + this.controller.executeHandler = this._execute.bind(this); + this.controller.supportsExecutionOrder = true; + this.controller.supportedLanguages = ['typescript', 'javascript']; + this.controller.onDidChangeSelectedNotebooks(e => { + if (e.selected) { + this.associatedNotebooks.add(e.notebook.uri.toString()); + } else { + this.associatedNotebooks.delete(e.notebook.uri.toString()); + } + }); + } + + protected async _execute(cells: vscode.NotebookCell[]): Promise { + for (const cell of cells) { + await this._runCell(cell); + } + } + + protected async _runCell(cell: vscode.NotebookCell) { + // create a single output with exec order 1 and output is plain/text + // of either the cell itself or (iff empty) the cell's document's uri + const task = this.controller.createNotebookCellExecution(cell); + task.start(Date.now()); + task.executionOrder = 1; + await sleep(10); // Force to be take some time + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain') + ])]); + task.end(true); + } +} + + +async function assertKernel(kernel: Kernel, notebook: vscode.NotebookDocument): Promise { + const success = await vscode.commands.executeCommand('notebook.selectKernel', { + extension: 'vscode.vscode-api-tests', + id: kernel.controller.id + }); + assert.ok(success, `expected selected kernel to be ${kernel.controller.id}`); + assert.ok(kernel.associatedNotebooks.has(notebook.uri.toString())); +} + +const apiTestContentProvider: vscode.NotebookContentProvider = { + openNotebook: async (resource: vscode.Uri): Promise => { + if (/.*empty\-.*\.vsctestnb$/.test(resource.path)) { + return { + metadata: {}, + cells: [] + }; + } + + const dto: vscode.NotebookData = { + metadata: { custom: { testMetadata: false } }, + cells: [ + { + value: 'test', + languageId: 'typescript', + kind: vscode.NotebookCellKind.Code, + outputs: [], + metadata: { custom: { testCellMetadata: 123 } }, + executionSummary: { timing: { startTime: 10, endTime: 20 } } + }, + { + value: 'test2', + languageId: 'typescript', + kind: vscode.NotebookCellKind.Code, + outputs: [ + new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('Hello World', 'text/plain') + ], + { + testOutputMetadata: true, + ['text/plain']: { testOutputItemMetadata: true } + }) + ], + executionSummary: { executionOrder: 5, success: true }, + metadata: { custom: { testCellMetadata: 456 } } + } + ] + }; + return dto; + }, + saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { + return; + }, + backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => { + return { + id: '1', + delete: () => { } + }; + } +}; + +(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook Kernel API tests', function () { + + const testDisposables: vscode.Disposable[] = []; + const suiteDisposables: vscode.Disposable[] = []; + + suiteTeardown(async function () { + + assertNoRpc(); + + await revertAllDirty(); + await closeAllEditors(); + + disposeAll(suiteDisposables); + suiteDisposables.length = 0; + }); + + suiteSetup(function () { + suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); + }); + + let defaultKernel: Kernel; + + setup(async function () { + // there should be ONE default kernel in this suite + defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); + testDisposables.push(defaultKernel.controller); + await saveAllFilesAndCloseAll(); + }); + + teardown(async function () { + disposeAll(testDisposables); + testDisposables.length = 0; + await saveAllFilesAndCloseAll(); + }); + + test('cell execute command takes arguments', async () => { + const notebook = await openRandomNotebookDocument(); + await vscode.window.showNotebookDocument(notebook); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.notebook.cellAt(0); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async event => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async event => { + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + await event; + assert.strictEqual(cell.outputs.length, 0, 'should clear'); + }); + + const secondResource = await createRandomNotebookFile(); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async event => { + await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, notebook.uri); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(vscode.window.activeNotebookEditor?.notebook.uri.fsPath, secondResource.fsPath); + }); + }); + + test('cell execute command takes arguments 2', async () => { + const notebook = await openRandomNotebookDocument(); + await vscode.window.showNotebookDocument(notebook); + + let firstCellExecuted = false; + let secondCellExecuted = false; + + const def = new DeferredPromise(); + testDisposables.push(vscode.workspace.onDidChangeNotebookDocument(e => { + e.cellChanges.forEach(change => { + if (change.cell.index === 0 && change.executionSummary) { + firstCellExecuted = true; + } + + if (change.cell.index === 1 && change.executionSummary) { + secondCellExecuted = true; + } + }); + + if (firstCellExecuted && secondCellExecuted) { + def.complete(); + } + })); + + vscode.commands.executeCommand('notebook.cell.execute', { document: notebook.uri, ranges: [{ start: 0, end: 1 }, { start: 1, end: 2 }] }); + + await def.p; + await saveAllFilesAndCloseAll(); + }); + + test('document execute command takes arguments', async () => { + const notebook = await openRandomNotebookDocument(); + await vscode.window.showNotebookDocument(notebook); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.notebook.cellAt(0); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { + await vscode.commands.executeCommand('notebook.execute', notebook.uri); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + }); + + test('cell execute and select kernel', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); + + const cell = editor.notebook.cellAt(0); + + const alternativeKernel = new class extends Kernel { + constructor() { + super('secondaryKernel', 'Notebook Secondary Test Kernel'); + this.controller.supportsExecutionOrder = false; + } + + override async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecution(cell); + task.start(); + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my second output', 'text/plain') + ])]); + task.end(true); + } + }; + testDisposables.push(alternativeKernel.controller); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { + await assertKernel(defaultKernel, notebook); + await vscode.commands.executeCommand('notebook.cell.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].items.length, 1); + assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), cell.document.getText()); + }); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { + await assertKernel(alternativeKernel, notebook); + await vscode.commands.executeCommand('notebook.cell.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].items.length, 1); + assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'my second output'); + }); + }); + + test('onDidChangeCellExecutionState is fired', async () => { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + const cell = editor.notebook.cellAt(0); + + let eventCount = 0; + const def = new DeferredPromise(); + testDisposables.push(vscode.notebooks.onDidChangeNotebookCellExecutionState(e => { + try { + assert.strictEqual(e.cell.document.uri.toString(), cell.document.uri.toString(), 'event should be fired for the executing cell'); + + if (eventCount === 0) { + assert.strictEqual(e.state, vscode.NotebookCellExecutionState.Pending, 'should be set to Pending'); + } else if (eventCount === 1) { + assert.strictEqual(e.state, vscode.NotebookCellExecutionState.Executing, 'should be set to Executing'); + assert.strictEqual(cell.outputs.length, 0, 'no outputs yet: ' + JSON.stringify(cell.outputs[0])); + } else if (e.state === vscode.NotebookCellExecutionState.Idle) { + assert.strictEqual(cell.outputs.length, 1, 'should have an output'); + def.complete(); + } + + eventCount++; + } catch (err) { + def.error(err); + } + })); + + vscode.commands.executeCommand('notebook.cell.execute', { document: notebook.uri, ranges: [{ start: 0, end: 1 }] }); + + await def.p; + }); + + test('Output changes are applied once the promise resolves', async function () { + + let called = false; + + const verifyOutputSyncKernel = new class extends Kernel { + + constructor() { + super('verifyOutputSyncKernel', ''); + } + + override async _execute(cells: vscode.NotebookCell[]) { + const [cell] = cells; + const task = this.controller.createNotebookCellExecution(cell); + task.start(); + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('Some output', 'text/plain') + ])]); + assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1); + assert.deepStrictEqual(new TextDecoder().decode(cell.notebook.cellAt(0).outputs[0].items[0].data), 'Some output'); + task.end(undefined); + called = true; + } + }; + + const notebook = await openRandomNotebookDocument(); + await vscode.window.showNotebookDocument(notebook); + await assertKernel(verifyOutputSyncKernel, notebook); + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(called, true); + verifyOutputSyncKernel.controller.dispose(); + }); + + test('executionSummary', async () => { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + const cell = editor.notebook.cellAt(0); + + assert.strictEqual(cell.executionSummary?.success, undefined); + assert.strictEqual(cell.executionSummary?.executionOrder, undefined); + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); + assert.ok(cell.executionSummary); + assert.strictEqual(cell.executionSummary!.success, true); + assert.strictEqual(typeof cell.executionSummary!.executionOrder, 'number'); + }); + + test('initialize executionSummary', async () => { + + const document = await openRandomNotebookDocument(); + const cell = document.cellAt(0); + + assert.strictEqual(cell.executionSummary?.success, undefined); + assert.strictEqual(cell.executionSummary?.timing?.startTime, 10); + assert.strictEqual(cell.executionSummary?.timing?.endTime, 20); + + }); + + test('execution cancelled when delete while executing', async () => { + const document = await openRandomNotebookDocument(); + const cell = document.cellAt(0); + + let executionWasCancelled = false; + const cancelledKernel = new class extends Kernel { + constructor() { + super('cancelledKernel', ''); + } + + override async _execute(cells: vscode.NotebookCell[]) { + const [cell] = cells; + const exe = this.controller.createNotebookCellExecution(cell); + exe.token.onCancellationRequested(() => executionWasCancelled = true); + } + }; + testDisposables.push(cancelledKernel.controller); + + await vscode.window.showNotebookDocument(document); + await assertKernel(cancelledKernel, document); + await vscode.commands.executeCommand('notebook.cell.execute'); + + // Delete executing cell + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(cell!.notebook.uri, new vscode.NotebookRange(cell!.index, cell!.index + 1), []); + await vscode.workspace.applyEdit(edit); + + assert.strictEqual(executionWasCancelled, true); + }); + + test('appendOutput to different cell', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + const cell0 = editor.notebook.cellAt(0); + const notebookEdit = new vscode.NotebookEdit(new vscode.NotebookRange(1, 1), [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'test 2', 'javascript')]); + const edit = new vscode.WorkspaceEdit(); + edit.set(notebook.uri, [notebookEdit]); + await vscode.workspace.applyEdit(edit); + const cell1 = editor.notebook.cellAt(1); + + const nextCellKernel = new class extends Kernel { + constructor() { + super('nextCellKernel', 'Append to cell kernel'); + } + + override async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecution(cell); + task.start(); + await task.appendOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my output') + ])], cell1); + await task.appendOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my output 2') + ])], cell1); + task.end(true); + } + }; + testDisposables.push(nextCellKernel.controller); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { + await assertKernel(nextCellKernel, notebook); + await vscode.commands.executeCommand('notebook.cell.execute'); + await event; + assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0'); + assert.strictEqual(cell1.outputs.length, 2, 'should update cell 1'); + assert.strictEqual(cell1.outputs[0].items.length, 1); + assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output'); + }); + }); + + test('replaceOutput to different cell', async function () { + const notebook = await openRandomNotebookDocument(); + const editor = await vscode.window.showNotebookDocument(notebook); + const cell0 = editor.notebook.cellAt(0); + const notebookEdit = new vscode.NotebookEdit(new vscode.NotebookRange(1, 1), [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'test 2', 'javascript')]); + const edit = new vscode.WorkspaceEdit(); + edit.set(notebook.uri, [notebookEdit]); + await vscode.workspace.applyEdit(edit); + const cell1 = editor.notebook.cellAt(1); + + const nextCellKernel = new class extends Kernel { + constructor() { + super('nextCellKernel', 'Replace to cell kernel'); + } + + override async _runCell(cell: vscode.NotebookCell) { + const task = this.controller.createNotebookCellExecution(cell); + task.start(); + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my output') + ])], cell1); + await task.replaceOutput([new vscode.NotebookCellOutput([ + vscode.NotebookCellOutputItem.text('my output 2') + ])], cell1); + task.end(true); + } + }; + testDisposables.push(nextCellKernel.controller); + + await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { + await assertKernel(nextCellKernel, notebook); + await vscode.commands.executeCommand('notebook.cell.execute'); + await event; + assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0'); + assert.strictEqual(cell1.outputs.length, 1, 'should update cell 1'); + assert.strictEqual(cell1.outputs[0].items.length, 1); + assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output 2'); + }); + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts deleted file mode 100644 index 270d9ea9aa2..00000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ /dev/null @@ -1,1137 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import { TextDecoder, TextEncoder } from 'util'; -import * as vscode from 'vscode'; -import { asPromise, assertNoRpc, closeAllEditors, createRandomFile, disposeAll, revertAllDirty, saveAllEditors } from '../utils'; - -async function createRandomNotebookFile() { - return createRandomFile('', undefined, '.vsctestnb'); -} - -async function openRandomNotebookDocument() { - const uri = await createRandomNotebookFile(); - return vscode.workspace.openNotebookDocument(uri); -} - -export async function saveAllFilesAndCloseAll() { - await saveAllEditors(); - await closeAllEditors(); -} - -async function withEvent(event: vscode.Event, callback: (e: Promise) => Promise) { - const e = asPromise(event); - await callback(e); -} - - -function sleep(ms: number): Promise { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - -export class Kernel { - - readonly controller: vscode.NotebookController; - - readonly associatedNotebooks = new Set(); - - constructor(id: string, label: string, viewType: string = 'notebookCoreTest') { - this.controller = vscode.notebooks.createNotebookController(id, viewType, label); - this.controller.executeHandler = this._execute.bind(this); - this.controller.supportsExecutionOrder = true; - this.controller.supportedLanguages = ['typescript', 'javascript']; - this.controller.onDidChangeSelectedNotebooks(e => { - if (e.selected) { - this.associatedNotebooks.add(e.notebook.uri.toString()); - } else { - this.associatedNotebooks.delete(e.notebook.uri.toString()); - } - }); - } - - protected async _execute(cells: vscode.NotebookCell[]): Promise { - for (const cell of cells) { - await this._runCell(cell); - } - } - - protected async _runCell(cell: vscode.NotebookCell) { - // create a single output with exec order 1 and output is plain/text - // of either the cell itself or (iff empty) the cell's document's uri - const task = this.controller.createNotebookCellExecution(cell); - task.start(Date.now()); - task.executionOrder = 1; - await sleep(10); // Force to be take some time - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain') - ])]); - task.end(true); - } -} - - -function getFocusedCell(editor?: vscode.NotebookEditor) { - return editor ? editor.notebook.cellAt(editor.selections[0].start) : undefined; -} - -async function assertKernel(kernel: Kernel, notebook: vscode.NotebookDocument): Promise { - const success = await vscode.commands.executeCommand('notebook.selectKernel', { - extension: 'vscode.vscode-api-tests', - id: kernel.controller.id - }); - assert.ok(success, `expected selected kernel to be ${kernel.controller.id}`); - assert.ok(kernel.associatedNotebooks.has(notebook.uri.toString())); -} - -const apiTestContentProvider: vscode.NotebookContentProvider = { - openNotebook: async (resource: vscode.Uri): Promise => { - if (/.*empty\-.*\.vsctestnb$/.test(resource.path)) { - return { - metadata: {}, - cells: [] - }; - } - - const dto: vscode.NotebookData = { - metadata: { custom: { testMetadata: false } }, - cells: [ - { - value: 'test', - languageId: 'typescript', - kind: vscode.NotebookCellKind.Code, - outputs: [], - metadata: { custom: { testCellMetadata: 123 } }, - executionSummary: { timing: { startTime: 10, endTime: 20 } } - }, - { - value: 'test2', - languageId: 'typescript', - kind: vscode.NotebookCellKind.Code, - outputs: [ - new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('Hello World', 'text/plain') - ], - { - testOutputMetadata: true, - ['text/plain']: { testOutputItemMetadata: true } - }) - ], - executionSummary: { executionOrder: 5, success: true }, - metadata: { custom: { testCellMetadata: 456 } } - } - ] - }; - return dto; - }, - saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { - return; - }, - saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => { - return; - }, - backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => { - return { - id: '1', - delete: () => { } - }; - } -}; - -(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests', function () { - - const testDisposables: vscode.Disposable[] = []; - const suiteDisposables: vscode.Disposable[] = []; - - suiteTeardown(async function () { - - assertNoRpc(); - - await revertAllDirty(); - await closeAllEditors(); - - disposeAll(suiteDisposables); - suiteDisposables.length = 0; - }); - - suiteSetup(function () { - suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); - }); - - let defaultKernel: Kernel; - - setup(async function () { - // there should be ONE default kernel in this suite - defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel'); - testDisposables.push(defaultKernel.controller); - await saveAllFilesAndCloseAll(); - }); - - teardown(async function () { - disposeAll(testDisposables); - testDisposables.length = 0; - await saveAllFilesAndCloseAll(); - }); - - test.skip('correct cell selection on undo/redo of cell creation', async function () { - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - await vscode.commands.executeCommand('undo'); - const selectionUndo = [...vscode.window.activeNotebookEditor!.selections]; - await vscode.commands.executeCommand('redo'); - const selectionRedo = vscode.window.activeNotebookEditor!.selections; - - // On undo, the selected cell must be the upper cell, ie the first one - assert.strictEqual(selectionUndo.length, 1); - assert.strictEqual(selectionUndo[0].start, 0); - assert.strictEqual(selectionUndo[0].end, 1); - // On redo, the selected cell must be the new cell, ie the second one - assert.strictEqual(selectionRedo.length, 1); - assert.strictEqual(selectionRedo[0].start, 1); - assert.strictEqual(selectionRedo[0].end, 2); - }); - - test('editor editing event', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - - const cellsChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.notebook, editor.notebook); - assert.strictEqual(cellChangeEventRet.contentChanges.length, 1); - assert.deepStrictEqual(cellChangeEventRet.contentChanges[0], { - range: new vscode.NotebookRange(1, 1), - removedCells: [], - addedCells: [editor.notebook.cellAt(1)] - }); - - const moveCellEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.moveUp'); - await moveCellEvent; - - const cellOutputChange = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.execute'); - const cellOutputsAddedRet = await cellOutputChange; - - assert.strictEqual(cellOutputsAddedRet.notebook.uri.toString(), editor.notebook.uri.toString()); - assert.strictEqual(cellOutputsAddedRet.metadata, undefined); - assert.strictEqual(cellOutputsAddedRet.contentChanges.length, 0); - assert.strictEqual(cellOutputsAddedRet.cellChanges.length, 1); - assert.deepStrictEqual(cellOutputsAddedRet.cellChanges[0].cell, editor.notebook.cellAt(0)); - assert.deepStrictEqual(cellOutputsAddedRet.cellChanges[0].executionSummary, { executionOrder: undefined, success: undefined, timing: undefined }); // TODO@jrieken should this be undefined instead all empty? - assert.strictEqual(cellOutputsAddedRet.cellChanges[0].document, undefined); - assert.strictEqual(cellOutputsAddedRet.cellChanges[0].metadata, undefined); - assert.strictEqual(cellOutputsAddedRet.cellChanges[0].outputs, undefined); - assert.strictEqual(cellOutputsAddedRet.cellChanges[0].cell.outputs.length, 1); - - const cellOutputClear = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - const cellOutputsCleardRet = await cellOutputClear; - assert.deepStrictEqual(cellOutputsCleardRet, { - notebook: editor.notebook, - metadata: undefined, - contentChanges: [], - cellChanges: [{ - cell: editor.notebook.cellAt(0), - document: undefined, - executionSummary: undefined, - metadata: undefined, - outputs: editor.notebook.cellAt(0).outputs - }], - }); - assert.strictEqual(cellOutputsCleardRet.cellChanges[0].cell.outputs.length, 0); - - // const cellChangeLanguage = getEventOncePromise(vscode.notebooks.onDidChangeCellLanguage); - // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); - // const cellChangeLanguageRet = await cellChangeLanguage; - // assert.deepStrictEqual(cellChangeLanguageRet, { - // document: vscode.window.activeNotebookEditor!.document, - // cells: vscode.window.activeNotebookEditor!.document.cellAt(0), - // language: 'markdown' - // }); - }); - - test('edit API batch edits', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - - const notebookChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - const version = editor.notebook.version; - const edit = new vscode.WorkspaceEdit(); - const cellEdit = vscode.NotebookEdit.replaceCells(new vscode.NotebookRange(1, 0), [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); - const metdataEdit = vscode.NotebookEdit.updateNotebookMetadata({ ...notebook.metadata, custom: { ...(notebook.metadata.custom || {}), extraNotebookMetadata: true } }); - edit.set(notebook.uri, [cellEdit, metdataEdit]); - await vscode.workspace.applyEdit(edit); - await notebookChangeEvent; - - const notebookChangeEvent2 = asPromise(vscode.workspace.onDidChangeNotebookDocument); - const edit2 = new vscode.WorkspaceEdit(); - const cellMetadataEdit = vscode.NotebookEdit.updateCellMetadata(0, { extraCellMetadata: true }); - edit2.set(notebook.uri, [cellMetadataEdit]); - await vscode.workspace.applyEdit(edit2); - await notebookChangeEvent2; - - assert.strictEqual(version + 2, editor.notebook.version); - const cell = editor.notebook.cellAt(0); - assert.ok(editor.notebook.metadata.custom.extraNotebookMetadata, `Test metadata not found`); - assert.ok(cell.metadata.extraCellMetadata, `Test cell metdata not found`); - }); - - test('edit API batch edits undo/redo', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - - const notebookChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - const version = editor.notebook.version; - await editor.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); - editBuilder.replaceCellMetadata(0, { inputCollapsed: false }); - }); - - await notebookChangeEvent; - assert.strictEqual(editor.notebook.cellCount, 3); - assert.strictEqual(editor.notebook.cellAt(0)?.metadata.inputCollapsed, false); - assert.strictEqual(version + 1, editor.notebook.version); - - await vscode.commands.executeCommand('undo'); - assert.strictEqual(version + 2, editor.notebook.version); - assert.strictEqual(editor.notebook.cellAt(0)?.metadata.inputCollapsed, undefined); - assert.strictEqual(editor.notebook.cellCount, 2); - }); - - // #126371 - test.skip('#98841, initialzation should not emit cell change events.', async function () { - let count = 0; - - testDisposables.push(vscode.workspace.onDidChangeNotebookDocument(() => { - count++; - })); - - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(count, 0); - }); - - test('notebook open', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - - const secondCell = editor.notebook.cellAt(1); - assert.strictEqual(secondCell.outputs.length, 1); - assert.deepStrictEqual(secondCell.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } }); - assert.strictEqual(secondCell.outputs[0].items.length, 1); - assert.strictEqual(secondCell.outputs[0].items[0].mime, 'text/plain'); - assert.strictEqual(new TextDecoder().decode(secondCell.outputs[0].items[0].data), 'Hello World'); - assert.strictEqual(secondCell.executionSummary?.executionOrder, 5); - assert.strictEqual(secondCell.executionSummary?.success, true); - }); - - test('notebook cell actions', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - - // ---- insert cell below and focus ---- // - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), ''); - - // ---- insert cell above and focus ---- // - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - let activeCell = getFocusedCell(editor); - assert.notStrictEqual(getFocusedCell(editor), undefined); - assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(editor.notebook.cellCount, 4); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); - - // ---- focus bottom ---- // - await vscode.commands.executeCommand('notebook.focusBottom'); - activeCell = getFocusedCell(editor); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 3); - - // ---- focus top and then copy down ---- // - await vscode.commands.executeCommand('notebook.focusTop'); - activeCell = getFocusedCell(editor); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 0); - - await vscode.commands.executeCommand('notebook.cell.copyDown'); - activeCell = getFocusedCell(editor); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); - assert.strictEqual(activeCell?.document.getText(), 'test'); - - { - const focusedCell = getFocusedCell(editor); - assert.strictEqual(focusedCell !== undefined, true); - // delete focused cell - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(focusedCell!.notebook.uri, new vscode.NotebookRange(focusedCell!.index, focusedCell!.index + 1), []); - await vscode.workspace.applyEdit(edit); - } - - activeCell = getFocusedCell(editor); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); - assert.strictEqual(activeCell?.document.getText(), ''); - - // ---- focus top and then copy up ---- // - await vscode.commands.executeCommand('notebook.focusTop'); - await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.strictEqual(editor.notebook.cellCount, 5); - assert.strictEqual(editor.notebook.cellAt(0).document.getText(), 'test'); - assert.strictEqual(editor.notebook.cellAt(1).document.getText(), 'test'); - assert.strictEqual(editor.notebook.cellAt(2).document.getText(), ''); - assert.strictEqual(editor.notebook.cellAt(3).document.getText(), ''); - activeCell = getFocusedCell(editor); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 0); - - - // ---- move up and down ---- // - - await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.strictEqual(editor.notebook.getCells().indexOf(getFocusedCell(editor)!), 1, - `first move down, active cell ${getFocusedCell(editor)!.document.uri.toString()}, ${getFocusedCell(editor)!.document.getText()}`); - - await vscode.commands.executeCommand('workbench.action.files.save'); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - }); - - test('editor move command - event and move cells will not recreate cells in ExtHost (#98126)', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - - const activeCell = getFocusedCell(editor); - assert.strictEqual(activeCell?.index, 0); - const notebookChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.ok(await notebookChangeEvent); - - const newActiveCell = getFocusedCell(editor); - assert.strictEqual(newActiveCell?.index, 1); - assert.deepStrictEqual(activeCell, newActiveCell); - }); - - // test('document runnable based on kernel count', async () => { - // const resource = await createRandomNotebookFile(); - // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // const editor = vscode.window.activeNotebookEditor!; - - // const cell = editor.notebook.cellAt(0); - // assert.strictEqual(cell.outputs.length, 0); - - // currentKernelProvider.setHasKernels(false); - // await vscode.commands.executeCommand('notebook.execute'); - // assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - - // currentKernelProvider.setHasKernels(true); - - // await withEvent(vscode.notebooks.onDidChangeCellOutputs, async (event) => { - // await vscode.commands.executeCommand('notebook.execute'); - // await event; - // assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - // }); - - // await saveAllFilesAndCloseAll(undefined); - // }); - - - // TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied - test.skip('cell execute command takes arguments', async () => { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.notebook.cellAt(0); - - await vscode.commands.executeCommand('notebook.execute'); - assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - }); - - test('cell execute command takes arguments 2', async () => { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.notebook.cellAt(0); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await vscode.commands.executeCommand('notebook.execute'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - }); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async event => { - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - await event; - assert.strictEqual(cell.outputs.length, 0, 'should clear'); - }); - - const secondResource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(vscode.window.activeNotebookEditor?.notebook.uri.fsPath, secondResource.fsPath); - }); - }); - - // #126371 - test.skip('cell execute command takes arguments ICellRange[]', async () => { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - vscode.commands.executeCommand('notebook.cell.execute', { ranges: [{ start: 0, end: 1 }, { start: 1, end: 2 }] }); - let firstCellExecuted = false; - let secondCellExecuted = false; - let resolve: () => void; - const p = new Promise(r => resolve = r); - const listener = vscode.workspace.onDidChangeNotebookDocument(e => { - e.cellChanges.forEach(change => { - if (change.cell.index === 0) { - firstCellExecuted = true; - } - - if (change.cell.index === 1) { - secondCellExecuted = true; - } - }); - - if (firstCellExecuted && secondCellExecuted) { - resolve(); - } - }); - - await p; - listener.dispose(); - await saveAllFilesAndCloseAll(); - }); - - test('document execute command takes arguments', async () => { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.notebook.cellAt(0); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await vscode.commands.executeCommand('notebook.execute'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - }); - - const clearChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - await clearChangeEvent; - assert.strictEqual(cell.outputs.length, 0, 'should clear'); - - const secondResource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await vscode.commands.executeCommand('notebook.execute', resource); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(vscode.window.activeNotebookEditor?.notebook.uri.fsPath, secondResource.fsPath); - }); - }); - - test('cell execute and select kernel', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); - - const cell = editor.notebook.cellAt(0); - - const alternativeKernel = new class extends Kernel { - constructor() { - super('secondaryKernel', 'Notebook Secondary Test Kernel'); - this.controller.supportsExecutionOrder = false; - } - - override async _runCell(cell: vscode.NotebookCell) { - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my second output', 'text/plain') - ])]); - task.end(true); - } - }; - testDisposables.push(alternativeKernel.controller); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await assertKernel(defaultKernel, notebook); - await vscode.commands.executeCommand('notebook.cell.execute'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(cell.outputs[0].items.length, 1); - assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); - assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), cell.document.getText()); - }); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await assertKernel(alternativeKernel, notebook); - await vscode.commands.executeCommand('notebook.cell.execute'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(cell.outputs[0].items.length, 1); - assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); - assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'my second output'); - }); - }); - - test.skip('onDidChangeCellExecutionState is fired', async () => { // TODO@rebornix https://github.com/microsoft/vscode/issues/139350 - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.notebook.cellAt(0); - - vscode.commands.executeCommand('notebook.cell.execute'); - let eventCount = 0; - let resolve: () => void; - const p = new Promise(r => resolve = r); - const listener = vscode.notebooks.onDidChangeNotebookCellExecutionState(e => { - if (eventCount === 0) { - assert.strictEqual(e.state, vscode.NotebookCellExecutionState.Pending, 'should be set to Pending'); - } else if (eventCount === 1) { - assert.strictEqual(e.state, vscode.NotebookCellExecutionState.Executing, 'should be set to Executing'); - assert.strictEqual(cell.outputs.length, 0, 'no outputs yet: ' + JSON.stringify(cell.outputs[0])); - } else if (eventCount === 2) { - assert.strictEqual(e.state, vscode.NotebookCellExecutionState.Idle, 'should be set to Idle'); - assert.strictEqual(cell.outputs.length, 1, 'should have an output'); - resolve(); - } - - eventCount++; - }); - - await p; - listener.dispose(); - }); - - test('notebook cell document workspace edit', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const activeCell = getFocusedCell(editor); - assert.notStrictEqual(getFocusedCell(editor), undefined); - assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(editor.notebook.cellCount, 4); - assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); - - await withEvent(vscode.workspace.onDidChangeTextDocument, async event => { - const edit = new vscode.WorkspaceEdit(); - edit.insert(activeCell!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - await event; - assert.strictEqual(vscode.window.activeNotebookEditor === editor, true); - assert.deepStrictEqual(editor.notebook.cellAt(1), getFocusedCell(editor)); - assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'var abc = 0;'); - }); - }); - - test.skip('multiple tabs: dirty + clean', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/140285 - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - const secondNotebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(secondNotebook); - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - - // make sure that the previous dirty editor is still restored in the extension host and no data loss - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 4); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); - - }); - - test.skip('multiple tabs: two dirty tabs and switching', async function () { - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); - - await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - const secondNotebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(secondNotebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); - - // switch to the first editor - await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 4); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); - - // switch to the second editor - await vscode.window.showNotebookDocument(secondNotebook); - assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 3); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); - - }); - - test('multiple tabs: different editors with same document', async function () { - - const notebook = await openRandomNotebookDocument(); - const firstNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.One }); - assert.ok(firstNotebookEditor === vscode.window.activeNotebookEditor); - - assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.languageId, 'typescript'); - - const secondNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.Beside }); - assert.strictEqual(secondNotebookEditor !== undefined, true, 'notebook first'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.getText(), 'test'); - assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.languageId, 'typescript'); - - assert.notStrictEqual(firstNotebookEditor, secondNotebookEditor); - assert.strictEqual(firstNotebookEditor?.notebook, secondNotebookEditor?.notebook, 'split notebook editors share the same document'); - - }); - - test.skip('#106657. Opening a notebook from markers view is broken ', async function () { - - const document = await openRandomNotebookDocument(); - const [cell] = document.getCells(); - - assert.strictEqual(vscode.window.activeNotebookEditor, undefined); - - // opening a cell-uri opens a notebook editor - await vscode.window.showTextDocument(cell.document, { viewColumn: vscode.ViewColumn.Active }); - // await vscode.commands.executeCommand('vscode.open', cell.document.uri, vscode.ViewColumn.Active); - - assert.strictEqual(!!vscode.window.activeNotebookEditor, true); - assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); - }); - - test('Cannot open notebook from cell-uri with vscode.open-command', async function () { - - const document = await openRandomNotebookDocument(); - const [cell] = document.getCells(); - - await saveAllFilesAndCloseAll(); - assert.strictEqual(vscode.window.activeNotebookEditor, undefined); - - // BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69) - // removes the fragment if it matches something numeric. For notebooks that's not wanted... - await vscode.commands.executeCommand('vscode.open', cell.document.uri); - - assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); - }); - - test('#97830, #97764. Support switch to other editor types', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(getFocusedCell(editor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'var abc = 0;'); - - // no kernel -> no default language - // assert.strictEqual(vscode.window.activeNotebookEditor!.kernel, undefined); - assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - - await vscode.commands.executeCommand('vscode.openWith', notebook.uri, 'default'); - assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, notebook.uri.path); - }); - - // open text editor, pin, and then open a notebook - test('#96105 - dirty editors', async function () { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); - const edit = new vscode.WorkspaceEdit(); - edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - // now it's dirty, open the resource with notebook editor should open a new one - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.notStrictEqual(vscode.window.activeNotebookEditor, undefined, 'notebook first'); - // assert.notStrictEqual(vscode.window.activeTextEditor, undefined); - - }); - - test('#102411 - untitled notebook creation failed', async function () { - await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); - assert.notStrictEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); - - await closeAllEditors(); - }); - - test('#102423 - copy/paste shares the same text buffer', async function () { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - - let activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(activeCell?.document.getText(), 'test'); - - await vscode.commands.executeCommand('notebook.cell.copyDown'); - await vscode.commands.executeCommand('notebook.cell.edit'); - activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.getCells().indexOf(activeCell!), 1); - assert.strictEqual(activeCell?.document.getText(), 'test'); - - const edit = new vscode.WorkspaceEdit(); - edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - - assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.getCells().length, 3); - assert.notStrictEqual(vscode.window.activeNotebookEditor!.notebook.cellAt(0).document.getText(), vscode.window.activeNotebookEditor!.notebook.cellAt(1).document.getText()); - - await closeAllEditors(); - }); - - test('#115855 onDidSaveNotebookDocument', async function () { - const resource = await createRandomNotebookFile(); - const notebook = await vscode.workspace.openNotebookDocument(resource); - const editor = await vscode.window.showNotebookDocument(notebook); - - const cellsChangeEvent = asPromise(vscode.workspace.onDidChangeNotebookDocument); - await editor.edit(editBuilder => { - editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); - }); - - const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.notebook === notebook, true); - assert.strictEqual(cellChangeEventRet.notebook.isDirty, true); - - const saveEvent = asPromise(vscode.workspace.onDidSaveNotebookDocument); - - await notebook.save(); - - await saveEvent; - assert.strictEqual(notebook.isDirty, false); - }); - - test('Output changes are applied once the promise resolves', async function () { - - let called = false; - - const verifyOutputSyncKernel = new class extends Kernel { - - constructor() { - super('verifyOutputSyncKernel', ''); - } - - override async _execute(cells: vscode.NotebookCell[]) { - const [cell] = cells; - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('Some output', 'text/plain') - ])]); - assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1); - assert.deepStrictEqual(new TextDecoder().decode(cell.notebook.cellAt(0).outputs[0].items[0].data), 'Some output'); - task.end(undefined); - called = true; - } - }; - - const notebook = await openRandomNotebookDocument(); - await vscode.window.showNotebookDocument(notebook); - await assertKernel(verifyOutputSyncKernel, notebook); - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.strictEqual(called, true); - verifyOutputSyncKernel.controller.dispose(); - }); - - test('executionSummary', async () => { - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const editor = vscode.window.activeNotebookEditor!; - const cell = editor.notebook.cellAt(0); - - assert.strictEqual(cell.executionSummary?.success, undefined); - assert.strictEqual(cell.executionSummary?.executionOrder, undefined); - await vscode.commands.executeCommand('notebook.cell.execute'); - assert.strictEqual(cell.outputs.length, 1, 'should execute'); - assert.ok(cell.executionSummary); - assert.strictEqual(cell.executionSummary!.success, true); - assert.strictEqual(typeof cell.executionSummary!.executionOrder, 'number'); - }); - - test('initialize executionSummary', async () => { - - const document = await openRandomNotebookDocument(); - const cell = document.cellAt(0); - - assert.strictEqual(cell.executionSummary?.success, undefined); - assert.strictEqual(cell.executionSummary?.timing?.startTime, 10); - assert.strictEqual(cell.executionSummary?.timing?.endTime, 20); - - }); - - test('execution cancelled when delete while executing', async () => { - const document = await openRandomNotebookDocument(); - const cell = document.cellAt(0); - - let executionWasCancelled = false; - const cancelledKernel = new class extends Kernel { - constructor() { - super('cancelledKernel', ''); - } - - override async _execute(cells: vscode.NotebookCell[]) { - const [cell] = cells; - const exe = this.controller.createNotebookCellExecution(cell); - exe.token.onCancellationRequested(() => executionWasCancelled = true); - } - }; - testDisposables.push(cancelledKernel.controller); - - await vscode.window.showNotebookDocument(document); - await assertKernel(cancelledKernel, document); - await vscode.commands.executeCommand('notebook.cell.execute'); - - // Delete executing cell - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCells(cell!.notebook.uri, new vscode.NotebookRange(cell!.index, cell!.index + 1), []); - await vscode.workspace.applyEdit(edit); - - assert.strictEqual(executionWasCancelled, true); - }); - - test('appendOutput to different cell', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - const cell0 = editor.notebook.cellAt(0); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cell1 = editor.notebook.cellAt(1); - - const nextCellKernel = new class extends Kernel { - constructor() { - super('nextCellKernel', 'Append to cell kernel'); - } - - override async _runCell(cell: vscode.NotebookCell) { - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - await task.appendOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my output') - ])], cell1); - await task.appendOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my output 2') - ])], cell1); - task.end(true); - } - }; - testDisposables.push(nextCellKernel.controller); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await assertKernel(nextCellKernel, notebook); - await vscode.commands.executeCommand('notebook.cell.execute'); - await event; - assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0'); - assert.strictEqual(cell1.outputs.length, 2, 'should update cell 1'); - assert.strictEqual(cell1.outputs[0].items.length, 1); - assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output'); - }); - }); - - test('replaceOutput to different cell', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - const cell0 = editor.notebook.cellAt(0); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cell1 = editor.notebook.cellAt(1); - - const nextCellKernel = new class extends Kernel { - constructor() { - super('nextCellKernel', 'Replace to cell kernel'); - } - - override async _runCell(cell: vscode.NotebookCell) { - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my output') - ])], cell1); - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('my output 2') - ])], cell1); - task.end(true); - } - }; - testDisposables.push(nextCellKernel.controller); - - await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { - await assertKernel(nextCellKernel, notebook); - await vscode.commands.executeCommand('notebook.cell.execute'); - await event; - assert.strictEqual(cell0.outputs.length, 0, 'should not change cell 0'); - assert.strictEqual(cell1.outputs.length, 1, 'should update cell 1'); - assert.strictEqual(cell1.outputs[0].items.length, 1); - assert.deepStrictEqual(new TextDecoder().decode(cell1.outputs[0].items[0].data), 'my output 2'); - }); - }); -}); - -(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('statusbar', () => { - const emitter = new vscode.EventEmitter(); - const onDidCallProvide = emitter.event; - const suiteDisposables: vscode.Disposable[] = []; - suiteTeardown(async function () { - assertNoRpc(); - - await revertAllDirty(); - await closeAllEditors(); - - disposeAll(suiteDisposables); - suiteDisposables.length = 0; - }); - - suiteSetup(() => { - suiteDisposables.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider('notebookCoreTest', { - async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise { - emitter.fire(cell); - return []; - } - })); - - suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); - }); - - test.skip('provideCellStatusBarItems called on metadata change', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139324 - const provideCalled = asPromise(onDidCallProvide); - const resource = await createRandomNotebookFile(); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - await provideCalled; - - const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookCellMetadata(resource, 0, { inputCollapsed: true }); - vscode.workspace.applyEdit(edit); - await provideCalled; - }); -}); - -(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests (metadata)', function () { - const testDisposables: vscode.Disposable[] = []; - const suiteDisposables: vscode.Disposable[] = []; - - suiteTeardown(async function () { - assertNoRpc(); - - await revertAllDirty(); - await closeAllEditors(); - - disposeAll(suiteDisposables); - suiteDisposables.length = 0; - }); - - suiteSetup(function () { - suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider)); - }); - - setup(async function () { - await saveAllFilesAndCloseAll(); - }); - - teardown(async function () { - disposeAll(testDisposables); - testDisposables.length = 0; - await saveAllFilesAndCloseAll(); - }); - - test('custom metadata should be supported', async function () { - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - - assert.strictEqual(editor.notebook.metadata.custom?.testMetadata, false); - assert.strictEqual(getFocusedCell(editor)?.metadata.custom?.testCellMetadata, 123); - assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - }); -}); - -suite('Notebook & LiveShare', function () { - - const suiteDisposables: vscode.Disposable[] = []; - const notebookType = 'vsls-testing'; - - suiteTeardown(() => { - vscode.Disposable.from(...suiteDisposables).dispose(); - }); - - suiteSetup(function () { - - suiteDisposables.push(vscode.workspace.registerNotebookSerializer(notebookType, new class implements vscode.NotebookSerializer { - deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): vscode.NotebookData | Thenable { - const value = new TextDecoder().decode(content); - const cell1 = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang'); - cell1.outputs = [new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(value)])]; - return new vscode.NotebookData([cell1]); - } - serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array | Thenable { - return new TextEncoder().encode(data.cells[0].value); - } - }, {}, { - displayName: 'LS', - filenamePattern: ['*'], - })); - }); - - test('command: vscode.resolveNotebookContentProviders', async function () { - - type Info = { viewType: string; displayName: string; filenamePattern: string[] }; - - const info = await vscode.commands.executeCommand('vscode.resolveNotebookContentProviders'); - assert.strictEqual(Array.isArray(info), true); - - const item = info.find(item => item.viewType === notebookType); - assert.ok(item); - assert.strictEqual(item?.viewType, notebookType); - }); - - test('command: vscode.executeDataToNotebook', async function () { - const value = 'dataToNotebook'; - const data = await vscode.commands.executeCommand('vscode.executeDataToNotebook', notebookType, new TextEncoder().encode(value)); - assert.ok(data instanceof vscode.NotebookData); - assert.strictEqual(data.cells.length, 1); - assert.strictEqual(data.cells[0].value, value); - assert.strictEqual(new TextDecoder().decode(data.cells[0].outputs![0].items[0].data), value); - }); - - test('command: vscode.executeNotebookToData', async function () { - const value = 'notebookToData'; - const notebook = new vscode.NotebookData([new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang')]); - const data = await vscode.commands.executeCommand('vscode.executeNotebookToData', notebookType, notebook); - assert.ok(data instanceof Uint8Array); - assert.deepStrictEqual(new TextDecoder().decode(data), value); - }); -}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 3fa4ed05599..b76e348e02e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual, doesNotThrow, equal, strictEqual, throws } from 'assert'; -import { ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalOptions, TerminalState, UIKind, window, workspace } from 'vscode'; +import { ConfigurationTarget, Disposable, env, EnvironmentVariableMutator, EnvironmentVariableMutatorType, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, window, workspace } from 'vscode'; import { assertNoRpc, poll } from '../utils'; // Disable terminal tests: @@ -223,7 +223,7 @@ import { assertNoRpc, poll } from '../utils'; await new Promise(r => { disposables.push(window.onDidCloseTerminal(t => { if (t === terminal) { - deepStrictEqual(t.exitStatus, { code: undefined }); + deepStrictEqual(t.exitStatus, { code: undefined, reason: TerminalExitReason.Extension }); r(); } })); @@ -579,7 +579,7 @@ import { assertNoRpc, poll } from '../utils'; strictEqual(created.exitStatus, undefined); disposables.push(window.onDidCloseTerminal(t2 => { if (t2 === created) { - deepStrictEqual(created.exitStatus, { code: undefined }); + deepStrictEqual(created.exitStatus, { code: undefined, reason: TerminalExitReason.Process }); r(); } })); @@ -604,7 +604,7 @@ import { assertNoRpc, poll } from '../utils'; strictEqual(created.exitStatus, undefined); disposables.push(window.onDidCloseTerminal(t2 => { if (t2 === created) { - deepStrictEqual(created.exitStatus, { code: 0 }); + deepStrictEqual(created.exitStatus, { code: 0, reason: TerminalExitReason.Process }); r(); } })); @@ -634,7 +634,7 @@ import { assertNoRpc, poll } from '../utils'; strictEqual(created.exitStatus, undefined); disposables.push(window.onDidCloseTerminal(t2 => { if (t2 === created) { - deepStrictEqual(created.exitStatus, { code: 22 }); + deepStrictEqual(created.exitStatus, { code: 22, reason: TerminalExitReason.Process }); r(); } })); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts deleted file mode 100644 index 470aee38205..00000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ /dev/null @@ -1,578 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as os from 'os'; -import * as vscode from 'vscode'; -import { assertNoRpc, closeAllEditors, delay, disposeAll } from '../utils'; - -const webviewId = 'myWebview'; - -function workspaceFile(...segments: string[]) { - return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments); -} - -suite('vscode API - webview', () => { - const disposables: vscode.Disposable[] = []; - - function _register(disposable: T) { - disposables.push(disposable); - return disposable; - } - - teardown(async () => { - assertNoRpc(); - await closeAllEditors(); - disposeAll(disposables); - }); - - test('webviews should be able to send and receive messages', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true })); - const firstResponse = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - webview.webview.postMessage({ value: 1 }); - assert.strictEqual((await firstResponse).value, 2); - }); - - test('webviews should not have scripts enabled by default', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {})); - const response = Promise.race([ - getMessage(webview), - new Promise<{}>(resolve => setTimeout(() => resolve({ value: '🎉' }), 1000)) - ]); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - assert.strictEqual((await response).value, '🎉'); - }); - - test('webviews should update html', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true })); - - { - const response = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - assert.strictEqual((await response).value, 'first'); - } - { - const response = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - assert.strictEqual((await response).value, 'second'); - } - }); - - test.skip('webviews should preserve vscode API state when they are hidden', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true })); - const ready = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - await ready; - - const firstResponse = await sendReceiveMessage(webview, { type: 'add' }); - assert.strictEqual(firstResponse.value, 1); - - // Swap away from the webview - const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json')); - await vscode.window.showTextDocument(doc); - - // And then back - const ready2 = getMessage(webview); - webview.reveal(vscode.ViewColumn.One); - await ready2; - - // We should still have old state - const secondResponse = await sendReceiveMessage(webview, { type: 'get' }); - assert.strictEqual(secondResponse.value, 1); - }); - - test.skip('webviews should preserve their context when they are moved between view columns', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/141001 - const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json')); - await vscode.window.showTextDocument(doc, vscode.ViewColumn.One); - - // Open webview in same column - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true })); - const ready = getMessage(webview); - webview.webview.html = statefulWebviewHtml; - await ready; - - const firstResponse = await sendReceiveMessage(webview, { type: 'add' }); - assert.strictEqual(firstResponse.value, 1); - - // Now move webview to new view column - webview.reveal(vscode.ViewColumn.Two); - - // We should still have old state - const secondResponse = await sendReceiveMessage(webview, { type: 'get' }); - assert.strictEqual(secondResponse.value, 1); - }); - - test.skip('webviews with retainContextWhenHidden should preserve their context when they are hidden', async function () { - this.retries(3); - - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - - webview.webview.html = statefulWebviewHtml; - await ready; - - const firstResponse = await sendReceiveMessage(webview, { type: 'add' }); - assert.strictEqual((await firstResponse).value, 1); - - // Swap away from the webview - const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json')); - await vscode.window.showTextDocument(doc); - - // And then back - webview.reveal(vscode.ViewColumn.One); - - // We should still have old state - const secondResponse = await sendReceiveMessage(webview, { type: 'get' }); - assert.strictEqual(secondResponse.value, 1); - }); - - test.skip('webviews with retainContextWhenHidden should preserve their page position when hidden', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - ${'

Header

'.repeat(200)} - `); - await ready; - - const firstResponse = getMessage(webview); - - assert.strictEqual(Math.round((await firstResponse).value), 100); - - // Swap away from the webview - const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json')); - await vscode.window.showTextDocument(doc); - - // And then back - webview.reveal(vscode.ViewColumn.One); - - // We should still have old scroll pos - const secondResponse = await sendReceiveMessage(webview, { type: 'get' }); - assert.strictEqual(Math.round(secondResponse.value), 100); - }); - - test.skip('webviews with retainContextWhenHidden should be able to recive messages while hidden', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/139960 - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - - webview.webview.html = statefulWebviewHtml; - await ready; - - const firstResponse = await sendReceiveMessage(webview, { type: 'add' }); - assert.strictEqual((await firstResponse).value, 1); - - // Swap away from the webview - const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json')); - await vscode.window.showTextDocument(doc); - - // Try posting a message to our hidden webview - const secondResponse = await sendReceiveMessage(webview, { type: 'add' }); - assert.strictEqual((await secondResponse).value, 2); - - // Now show webview again - webview.reveal(vscode.ViewColumn.One); - - // We should still have old state - const thirdResponse = await sendReceiveMessage(webview, { type: 'get' }); - assert.strictEqual(thirdResponse.value, 2); - }); - - - test.skip('webviews should only be able to load resources from workspace by default', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/139960 - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { - viewColumn: vscode.ViewColumn.One - }, { - enableScripts: true - })); - - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - const ready = getMessage(webview); - if ((await ready).userAgent.indexOf('Firefox') >= 0) { - // Skip on firefox web for now. - // Firefox service workers never seem to get any 'fetch' requests here. Other browsers work fine - return; - } - - { - const imagePath = webview.webview.asWebviewUri(workspaceFile('image.png')); - const response = await sendReceiveMessage(webview, { src: imagePath.toString() }); - assert.strictEqual(response.value, true); - } - // { - // // #102188. Resource filename containing special characters like '%', '#', '?'. - // const imagePath = webview.webview.asWebviewUri(workspaceFile('image%02.png')); - // const response = await sendReceiveMessage(webview, { src: imagePath.toString() }); - // assert.strictEqual(response.value, true); - // } - // { - // // #102188. Resource filename containing special characters like '%', '#', '?'. - // const imagePath = webview.webview.asWebviewUri(workspaceFile('image%.png')); - // const response = await sendReceiveMessage(webview, { src: imagePath.toString() }); - // assert.strictEqual(response.value, true); - // } - { - const imagePath = webview.webview.asWebviewUri(workspaceFile('no-such-image.png')); - const response = await sendReceiveMessage(webview, { src: imagePath.toString() }); - assert.strictEqual(response.value, false); - } - { - const imagePath = webview.webview.asWebviewUri(workspaceFile('..', '..', '..', 'resources', 'linux', 'code.png')); - const response = await sendReceiveMessage(webview, { src: imagePath.toString() }); - assert.strictEqual(response.value, false); - } - }); - - test.skip('webviews should allow overriding allowed resource paths using localResourceRoots', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { - enableScripts: true, - localResourceRoots: [workspaceFile('sub')] - })); - - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - { - const response = sendReceiveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('sub', 'image.png')).toString() }); - assert.strictEqual((await response).value, true); - } - { - const response = sendReceiveMessage(webview, { src: webview.webview.asWebviewUri(workspaceFile('image.png')).toString() }); - assert.strictEqual((await response).value, false); - } - }); - - test.skip('webviews using hard-coded old style vscode-resource uri should work', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/139572 - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { - enableScripts: true, - localResourceRoots: [workspaceFile('sub')] - })); - - const imagePath = workspaceFile('sub', 'image.png').with({ scheme: 'vscode-resource' }).toString(); - - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - - `); - - const ready = getMessage(webview); - if ((await ready).userAgent.indexOf('Firefox') >= 0) { - // Skip on firefox web for now. - // Firefox service workers never seem to get any 'fetch' requests here. Other browsers work fine - return; - } - const firstResponse = await sendReceiveMessage(webview, { src: imagePath.toString() }); - - assert.strictEqual(firstResponse.value, true); - }); - - test('webviews should have real view column after they are created, #56097', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.Active }, { enableScripts: true })); - - // Since we used a symbolic column, we don't know what view column the webview will actually show in at first - assert.strictEqual(webview.viewColumn, undefined); - - let changed = false; - const viewStateChanged = new Promise((resolve) => { - webview.onDidChangeViewState(e => { - if (changed) { - throw new Error('Only expected a single view state change'); - } - changed = true; - resolve(e); - }, undefined, disposables); - }); - - assert.strictEqual((await viewStateChanged).webviewPanel.viewColumn, vscode.ViewColumn.One); - - const firstResponse = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - - webview.webview.postMessage({ value: 1 }); - await firstResponse; - assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One); - }); - - if (os.platform() === 'darwin') { - test.skip('webview can copy text from webview', async () => { - const expectedText = `webview text from: ${Date.now()}!`; - - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - - - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - ${expectedText} - `); - await ready; - - await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); - await delay(200); // Make sure copy has time to reach webview - assert.strictEqual(await vscode.env.clipboard.readText(), expectedText); - }); - } - - test.skip('webviews should transfer ArrayBuffers to and from webviews', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - await ready; - - const responsePromise = getMessage(webview); - - const bufferLen = 100; - - { - const arrayBuffer = new ArrayBuffer(bufferLen); - const uint8Array = new Uint8Array(arrayBuffer); - for (let i = 0; i < bufferLen; ++i) { - uint8Array[i] = i; - } - webview.webview.postMessage({ - type: 'add1', - array: arrayBuffer - }); - } - { - const response = await responsePromise; - assert.ok(response.array instanceof ArrayBuffer); - - const uint8Array = new Uint8Array(response.array); - for (let i = 0; i < bufferLen; ++i) { - assert.strictEqual(uint8Array[i], i + 1); - } - } - }); - - test.skip('webviews should transfer Typed arrays to and from webviews', async () => { - const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true })); - const ready = getMessage(webview); - webview.webview.html = createHtmlDocumentWithBody(/*html*/` - `); - await ready; - - const responsePromise = getMessage(webview); - - const bufferLen = 100; - { - const arrayBuffer = new ArrayBuffer(bufferLen); - const uint8Array = new Uint8Array(arrayBuffer); - const uint16Array = new Uint16Array(arrayBuffer); - for (let i = 0; i < uint16Array.length; ++i) { - uint16Array[i] = i; - } - - webview.webview.postMessage({ - type: 'add1', - array1: uint8Array, - array2: uint16Array, - }); - } - { - const response = await responsePromise; - - assert.ok(response.array1 instanceof Uint8Array); - assert.ok(response.array2 instanceof Uint16Array); - assert.ok(response.array1.buffer === response.array2.buffer); - - const uint8Array = response.array1; - for (let i = 0; i < bufferLen; ++i) { - if (i % 2 === 0) { - assert.strictEqual(uint8Array[i], Math.floor(i / 2) + 1); - } else { - assert.strictEqual(uint8Array[i], 0); - } - } - } - }); -}); - -function createHtmlDocumentWithBody(body: string): string { - return /*html*/` - - - - - - Document - - - ${body} - -`; -} - -const statefulWebviewHtml = createHtmlDocumentWithBody(/*html*/ ` - `); - - -function getMessage(webview: vscode.WebviewPanel): Promise { - return new Promise(resolve => { - const sub = webview.webview.onDidReceiveMessage(message => { - sub.dispose(); - resolve(message); - }); - }); -} - -function sendReceiveMessage(webview: vscode.WebviewPanel, message: T): Promise { - const p = getMessage(webview); - webview.webview.postMessage(message); - return p; -} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index fd3c9a0d226..5a9825d77b9 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -207,9 +207,25 @@ suite('vscode API - window', () => { } })); + // verify the result array matches our expectations: depending + // on execution time there are 2 possible results for the first + // two entries. For the last entry there is only the `fileC` URI + // as expected result because it is the last editor opened. + // - either `undefined` indicating that the opening of the editor + // was cancelled by the next editor opening + // - or the expected `URI` that was opened in case it suceeds + assert.strictEqual(result.length, 3); - assert.strictEqual(result[0], undefined); - assert.strictEqual(result[1], undefined); + if (result[0]) { + assert.strictEqual(result[0].toString(), fileA.toString()); + } else { + assert.strictEqual(result[0], undefined); + } + if (result[1]) { + assert.strictEqual(result[1].toString(), fileB.toString()); + } else { + assert.strictEqual(result[1], undefined); + } assert.strictEqual(result[2]?.toString(), fileC.toString()); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index a055cf45520..833292a2c28 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -139,6 +139,8 @@ suite('vscode API - workspace', () => { assert.strictEqual(didSave, true, `FAILED to save${doc.uri.toString()}`); + assert.ok(closed); + assert.ok(closed.uri.toString() === doc.uri.toString(), `closed.uri = ${closed.uri.toString()} but doc.uri = ${doc.uri.toString()}`); assert.ok(closed === doc); assert.ok(!doc.isDirty); assert.ok(fs.existsSync(path)); @@ -286,9 +288,6 @@ suite('vscode API - workspace', () => { sub.dispose(); }); - function assertEqualPath(a: string, b: string): void { - assert.ok(pathEquals(a, b), `${a} <-> ${b}`); - } test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => { const file = await createRandomFile(); @@ -296,23 +295,20 @@ suite('vscode API - workspace', () => { await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - const pendingAsserts: Function[] = []; - let onDidOpenTextDocument = false; + const onDidOpenTextDocument = new Set(); + const onDidChangeTextDocument = new Set(); + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidOpenTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidOpenTextDocument = true; + onDidOpenTextDocument.add(e); })); - let onDidChangeTextDocument = false; disposables.push(vscode.workspace.onDidChangeTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath)); - onDidChangeTextDocument = true; + onDidChangeTextDocument.add(e.document); })); - let onDidSaveTextDocument = false; disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -323,10 +319,10 @@ suite('vscode API - workspace', () => { }); await doc.save(); - assert.ok(onDidOpenTextDocument); - assert.ok(onDidChangeTextDocument); - assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(assert => assert()); + assert.ok(Array.from(onDidOpenTextDocument).find(e => e.uri.toString() === file.toString()), 'did Open: ' + file.toString()); + assert.ok(Array.from(onDidChangeTextDocument).find(e => e.uri.toString() === file.toString()), 'did Change: ' + file.toString()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); + disposeAll(disposables); return deleteFile(file); }); @@ -334,14 +330,13 @@ suite('vscode API - workspace', () => { test('events: onDidSaveTextDocument fires even for non dirty file when saved', async () => { const file = await createRandomFile(); const disposables: vscode.Disposable[] = []; - const pendingAsserts: Function[] = []; await revertAllDirty(); // needed for a clean state for `onDidSaveTextDocument` (#102365) - let onDidSaveTextDocument = false; + const onDidSaveTextDocument = new Set(); + disposables.push(vscode.workspace.onDidSaveTextDocument(e => { - pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath)); - onDidSaveTextDocument = true; + onDidSaveTextDocument.add(e); })); const doc = await vscode.workspace.openTextDocument(file); @@ -349,7 +344,7 @@ suite('vscode API - workspace', () => { await vscode.commands.executeCommand('workbench.action.files.save'); assert.ok(onDidSaveTextDocument); - pendingAsserts.forEach(fn => fn()); + assert.ok(Array.from(onDidSaveTextDocument).find(e => e.uri.toString() === file.toString()), 'did Save: ' + file.toString()); disposeAll(disposables); return deleteFile(file); }); @@ -1154,4 +1149,24 @@ suite('vscode API - workspace', () => { assert.strictEqual(document.isDirty, false); } }); + + test('SnippetString in WorkspaceEdit', async function (): Promise { + const file = await createRandomFile('hello\nworld'); + + const document = await vscode.workspace.openTextDocument(file); + const edt = await vscode.window.showTextDocument(document); + + assert.ok(edt === vscode.window.activeTextEditor); + + const we = new vscode.WorkspaceEdit(); + we.replace(document.uri, new vscode.Range(0, 0, 0, 0), new vscode.SnippetString('${1:foo}${2:bar}')); + const success = await vscode.workspace.applyEdit(we); + if (edt !== vscode.window.activeTextEditor) { + return this.skip(); + } + + assert.ok(success); + assert.strictEqual(document.getText(), 'foobarhello\nworld'); + assert.deepStrictEqual(edt.selections, [new vscode.Selection(0, 0, 0, 3)]); + }); }); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index c5a8e58483f..7c506694289 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -125,11 +125,12 @@ export function assertNoRpcFromEntry(entry: [obj: any, name: string]) { } export async function asPromise(event: vscode.Event, timeout = vscode.env.uiKind === vscode.UIKind.Desktop ? 5000 : 15000): Promise { + const error = new Error('asPromise TIMEOUT reached'); return new Promise((resolve, reject) => { const handle = setTimeout(() => { sub.dispose(); - reject(new Error('asPromise TIMEOUT reached')); + reject(error); }, timeout); const sub = event(e => { @@ -183,3 +184,61 @@ export async function poll( trial++; } } + +export type ValueCallback = (value: T | Promise) => void; + +/** + * Creates a promise whose resolution or rejection can be controlled imperatively. + */ +export class DeferredPromise { + + private completeCallback!: ValueCallback; + private errorCallback!: (err: unknown) => void; + private rejected = false; + private resolved = false; + + public get isRejected() { + return this.rejected; + } + + public get isResolved() { + return this.resolved; + } + + public get isSettled() { + return this.rejected || this.resolved; + } + + public readonly p: Promise; + + constructor() { + this.p = new Promise((c, e) => { + this.completeCallback = c; + this.errorCallback = e; + }); + } + + public complete(value: T) { + return new Promise(resolve => { + this.completeCallback(value); + this.resolved = true; + resolve(); + }); + } + + public error(err: unknown) { + return new Promise(resolve => { + this.errorCallback(err); + this.rejected = true; + resolve(); + }); + } + + public cancel() { + new Promise(resolve => { + this.errorCallback(new Error('Canceled')); + this.rejected = true; + resolve(); + }); + } +} diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/makefile b/extensions/vscode-colorize-tests/test/colorize-fixtures/makefile index 3f77d04fa52..32daa0c97af 100644 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/makefile +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/makefile @@ -77,6 +77,10 @@ var!=echo val var:=val \ notvar=butval var:=$(val:.c=.o) +var:=blah#comment +var?=blah\#not_a_comment +var:=blah\\#comment +var!=blah\\\#not_a_comment var-$(nested-var)=val diff --git a/extensions/vscode-colorize-tests/test/colorize-results/makefile.json b/extensions/vscode-colorize-tests/test/colorize-results/makefile.json index a3abf6bf666..ecaa0f00373 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/makefile.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/makefile.json @@ -252,7 +252,31 @@ } }, { - "c": " second,second", + "c": " second", + "t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile string.interpolated.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": ",", + "t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile string.interpolated.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": "second", "t": "source.makefile meta.scope.target.makefile meta.scope.prerequisites.makefile string.interpolated.makefile string.interpolated.makefile meta.scope.function-call.makefile", "r": { "dark_plus": "string: #CE9178", @@ -1572,7 +1596,7 @@ } }, { - "c": " undefined,", + "c": " undefined", "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile", "r": { "dark_plus": "string: #CE9178", @@ -1583,6 +1607,18 @@ "hc_light": "string: #0F4A85" } }, + { + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, { "c": "$(", "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", @@ -1680,7 +1716,43 @@ } }, { - "c": ",0,1", + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": "0", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": "1", "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile meta.scope.function-call.makefile", "r": { "dark_plus": "string: #CE9178", @@ -1800,7 +1872,31 @@ } }, { - "c": " defined,TOP_DIR", + "c": " defined", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": "TOP_DIR", "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile", "r": { "dark_plus": "string: #CE9178", @@ -1836,7 +1932,19 @@ } }, { - "c": ",0)", + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "0)", "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", "r": { "dark_plus": "default: #D4D4D4", @@ -2076,7 +2184,31 @@ } }, { - "c": " defined,CODIT_DIR", + "c": " defined", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "hc_light": "string: #0F4A85" + } + }, + { + "c": "CODIT_DIR", "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile meta.scope.function-call.makefile", "r": { "dark_plus": "string: #CE9178", @@ -2112,7 +2244,19 @@ } }, { - "c": ",0)", + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "0)", "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", "r": { "dark_plus": "default: #D4D4D4", @@ -2688,7 +2832,31 @@ } }, { - "c": "\", \"skip\")", + "c": "\"", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": ",", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile punctuation.separator.delimeter.comma.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": " \"skip\")", "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", "r": { "dark_plus": "default: #D4D4D4", @@ -3359,6 +3527,198 @@ "hc_light": "string: #0F4A85" } }, + { + "c": "var", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "hc_light": "variable: #001080" + } + }, + { + "c": ":=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "blah", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "#", + "t": "source.makefile comment.line.number-sign.makefile punctuation.definition.comment.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "hc_light": "comment: #515151" + } + }, + { + "c": "comment", + "t": "source.makefile comment.line.number-sign.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "hc_light": "comment: #515151" + } + }, + { + "c": "var", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "hc_light": "variable: #001080" + } + }, + { + "c": "?=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "blah\\#not_a_comment", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "var", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "hc_light": "variable: #001080" + } + }, + { + "c": ":=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "blah\\\\", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "#", + "t": "source.makefile comment.line.number-sign.makefile punctuation.definition.comment.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "hc_light": "comment: #515151" + } + }, + { + "c": "comment", + "t": "source.makefile comment.line.number-sign.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668", + "hc_light": "comment: #515151" + } + }, + { + "c": "var", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "hc_light": "variable: #001080" + } + }, + { + "c": "!=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, + { + "c": "blah\\\\\\#not_a_comment", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "hc_light": "default: #292929" + } + }, { "c": "var-", "t": "source.makefile variable.other.makefile", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json index 2d184cc6f8b..e226b96334c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-78769_cpp.json @@ -61,7 +61,7 @@ }, { "c": "der", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -73,7 +73,7 @@ }, { "c": ",", - "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.parameters.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp punctuation.separator.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -85,7 +85,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -97,7 +97,7 @@ }, { "c": "base", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -109,7 +109,7 @@ }, { "c": ",", - "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.parameters.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp punctuation.separator.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -121,7 +121,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -133,7 +133,7 @@ }, { "c": "func", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -145,7 +145,7 @@ }, { "c": ",", - "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.parameters.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp punctuation.separator.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -157,7 +157,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -169,7 +169,7 @@ }, { "c": "decorators", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -217,19 +217,19 @@ }, { "c": " ", - "t": "source.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "namespace", - "t": "source.cpp meta.block.namespace.cpp meta.head.namespace.cpp keyword.other.namespace.definition.cpp storage.type.namespace.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.head.namespace.cpp keyword.other.namespace.definition.cpp storage.type.namespace.definition.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -241,67 +241,67 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.head.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.head.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "{", - "t": "source.cpp meta.block.namespace.cpp meta.head.namespace.cpp punctuation.section.block.begin.bracket.curly.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.head.namespace.cpp punctuation.section.block.begin.bracket.curly.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "struct", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp storage.type.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp storage.type.struct.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -313,67 +313,67 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "der", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp entity.name.type.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp entity.name.type.struct.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.type: #4EC9B0", "hc_light": "entity.name.type: #185E73" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ":", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp punctuation.separator.colon.inheritance.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp punctuation.separator.colon.inheritance.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "public", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp storage.type.modifier.access.public.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp storage.type.modifier.access.public.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -385,115 +385,115 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "base", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp meta.qualified_type.cpp entity.name.type.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp meta.qualified_type.cpp entity.name.type.cpp", "r": { "dark_plus": "entity.name.type: #4EC9B0", "light_plus": "entity.name.type: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.type: #4EC9B0", "hc_light": "entity.name.type: #185E73" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "{", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp punctuation.section.block.begin.bracket.curly.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.head.struct.cpp punctuation.section.block.begin.bracket.curly.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "void", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -505,163 +505,163 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "f", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp entity.name.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp entity.name.function.definition.cpp", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA", "hc_light": "entity.name.function: #5E2CBC" } }, { "c": "(", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.begin.bracket.round.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.begin.bracket.round.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ")", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.end.bracket.round.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.end.bracket.round.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ";", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp punctuation.terminator.statement.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "}", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp punctuation.section.block.end.bracket.curly.struct.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp meta.body.struct.cpp punctuation.section.block.end.bracket.curly.struct.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ";", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.block.struct.cpp punctuation.terminator.statement.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "static", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp storage.modifier.static.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp storage.modifier.static.cpp", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -673,19 +673,19 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "void", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.qualified_type.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -697,499 +697,499 @@ }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "func", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp entity.name.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp entity.name.function.definition.cpp", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA", "hc_light": "entity.name.function: #5E2CBC" } }, { "c": "(", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.begin.bracket.round.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.begin.bracket.round.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ")", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.end.bracket.round.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.parameters.end.bracket.round.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "{", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.block.begin.bracket.curly.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.head.function.definition.cpp punctuation.section.block.begin.bracket.curly.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " der v", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ";", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "v", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp variable.other.object.access.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp variable.other.object.access.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE", "hc_light": "variable: #001080" } }, { "c": ".", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.separator.dot-access.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.separator.dot-access.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "f", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp entity.name.function.member.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp entity.name.function.member.cpp", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA", "hc_light": "entity.name.function: #5E2CBC" } }, { "c": "(", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.begin.bracket.round.function.member.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.begin.bracket.round.function.member.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ")", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.end.bracket.round.function.member.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.arguments.end.bracket.round.function.member.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ";", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.terminator.statement.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "}", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.block.end.bracket.curly.function.definition.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp meta.function.definition.cpp meta.body.function.definition.cpp punctuation.section.block.end.bracket.curly.function.definition.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "DOCTEST_REGISTER_FUNCTION", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp entity.name.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp entity.name.function.call.cpp", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA", "hc_light": "entity.name.function: #5E2CBC" } }, { "c": "(", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "DOCTEST_EMPTY", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ",", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.separator.delimiter.comma.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " func", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ",", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.separator.delimiter.comma.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.separator.delimiter.comma.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " decorators", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ")", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "}", - "t": "source.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.block.end.bracket.curly.namespace.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.block.namespace.cpp meta.body.namespace.cpp punctuation.section.block.end.bracket.curly.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": " ", - "t": "source.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "\\", - "t": "source.cpp constant.character.escape.line-continuation.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp constant.character.escape.line-continuation.cpp", "r": { "dark_plus": "constant.character.escape: #D7BA7D", "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "constant.character: #569CD6", "hc_light": "constant.character.escape: #EE0000" } }, { "c": " ", - "t": "source.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "inline", - "t": "source.cpp storage.modifier.specifier.functional.pre-parameters.inline.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp storage.modifier.specifier.functional.pre-parameters.inline.cpp", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -1201,19 +1201,19 @@ }, { "c": " DOCTEST_NOINLINE ", - "t": "source.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "void", - "t": "source.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp storage.type.primitive.cpp storage.type.built-in.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -1225,74 +1225,74 @@ }, { "c": " ", - "t": "source.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "der", - "t": "source.cpp entity.name.scope-resolution.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp entity.name.scope-resolution.cpp", "r": { "dark_plus": "entity.name.scope-resolution: #4EC9B0", "light_plus": "entity.name.scope-resolution: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.scope-resolution: #4EC9B0", "hc_light": "entity.name.scope-resolution: #185E73" } }, { "c": "::", - "t": "source.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": "f", - "t": "source.cpp entity.name.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.call.cpp", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA", "hc_light": "entity.name.function: #5E2CBC" } }, { "c": "(", - "t": "source.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.section.arguments.begin.bracket.round.function.call.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } }, { "c": ")", - "t": "source.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp punctuation.section.arguments.end.bracket.round.function.call.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6", + "hc_light": "meta.preprocessor: #0F4A85" } } ] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json index f413a589570..c2bcf1d8c5f 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cpp.json @@ -1345,7 +1345,7 @@ }, { "c": "a", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", @@ -1357,7 +1357,7 @@ }, { "c": ",", - "t": "source.cpp meta.preprocessor.macro.cpp punctuation.separator.parameters.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp punctuation.separator.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -1369,7 +1369,7 @@ }, { "c": " ", - "t": "source.cpp meta.preprocessor.macro.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp", "r": { "dark_plus": "meta.preprocessor: #569CD6", "light_plus": "meta.preprocessor: #0000FF", @@ -1381,7 +1381,7 @@ }, { "c": "b", - "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", + "t": "source.cpp meta.preprocessor.macro.cpp meta.function.preprocessor.parameters.cpp variable.parameter.preprocessor.cpp", "r": { "dark_plus": "variable: #9CDCFE", "light_plus": "variable: #001080", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_fs.json b/extensions/vscode-colorize-tests/test/colorize-results/test_fs.json index e0ac27238d5..f417b2ef010 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_fs.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_fs.json @@ -733,7 +733,7 @@ }, { "c": "and", - "t": "source.fsharp keyword.fsharp", + "t": "source.fsharp binding.fsharp keyword.fsharp", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", diff --git a/extensions/vscode-custom-editor-tests/.vscodeignore b/extensions/vscode-custom-editor-tests/.vscodeignore deleted file mode 100644 index 9f1e0620775..00000000000 --- a/extensions/vscode-custom-editor-tests/.vscodeignore +++ /dev/null @@ -1,12 +0,0 @@ -test/** -test-workspace/** -src/** -tsconfig.json -out/test/** -out/** -extension.webpack.config.js -extension-browser.webpack.config.js -cgmanifest.json -yarn.lock -preview-src/** -webpack.config.js diff --git a/extensions/vscode-custom-editor-tests/customEditorMedia/textEditor.js b/extensions/vscode-custom-editor-tests/customEditorMedia/textEditor.js deleted file mode 100644 index 8978b201c9f..00000000000 --- a/extensions/vscode-custom-editor-tests/customEditorMedia/textEditor.js +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -(function () { - // @ts-ignore - const vscode = acquireVsCodeApi(); - - const textArea = document.querySelector('textarea'); - - const initialState = vscode.getState(); - if (initialState) { - textArea.value = initialState.value; - } - - window.addEventListener('message', e => { - switch (e.data.type) { - case 'fakeInput': - { - const value = e.data.value; - textArea.value = value; - onInput(); - break; - } - - case 'setValue': - { - const value = e.data.value; - textArea.value = value; - vscode.setState({ value }); - - vscode.postMessage({ - type: 'didChangeContent', - value: value - }); - break; - } - } - }); - - const onInput = () => { - const value = textArea.value; - vscode.setState({ value }); - vscode.postMessage({ - type: 'edit', - value: value - }); - vscode.postMessage({ - type: 'didChangeContent', - value: value - }); - }; - - textArea.addEventListener('input', onInput); -}()); diff --git a/extensions/vscode-custom-editor-tests/media/icon.png b/extensions/vscode-custom-editor-tests/media/icon.png deleted file mode 100644 index f785ef70316..00000000000 Binary files a/extensions/vscode-custom-editor-tests/media/icon.png and /dev/null differ diff --git a/extensions/vscode-custom-editor-tests/package.json b/extensions/vscode-custom-editor-tests/package.json deleted file mode 100644 index 06814bdceb1..00000000000 --- a/extensions/vscode-custom-editor-tests/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "vscode-custom-editor-tests", - "description": "Custom editor tests for VS Code", - "version": "0.0.1", - "publisher": "vscode", - "license": "MIT", - "private": true, - "activationEvents": [ - "onCustomEditor:testWebviewEditor.abc" - ], - "main": "./out/extension", - "engines": { - "vscode": "^1.48.0" - }, - "icon": "media/icon.png", - "scripts": { - "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-notebook-tests ./tsconfig.json" - }, - "dependencies": { - "p-limit": "^3.0.2" - }, - "devDependencies": { - "@types/mocha": "^9.1.1", - "@types/node": "16.x", - "@types/p-limit": "^2.2.0" - }, - "contributes": { - "customEditors": [ - { - "viewType": "testWebviewEditor.abc", - "displayName": "Test ABC editor", - "selector": [ - { - "filenamePattern": "*.abc" - } - ] - } - ] - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } -} diff --git a/extensions/vscode-custom-editor-tests/src/customTextEditor.ts b/extensions/vscode-custom-editor-tests/src/customTextEditor.ts deleted file mode 100644 index 17d2ee888ae..00000000000 --- a/extensions/vscode-custom-editor-tests/src/customTextEditor.ts +++ /dev/null @@ -1,174 +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 pLimit from 'p-limit'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Disposable } from './dispose'; - -export namespace Testing { - export const abcEditorContentChangeCommand = '_abcEditor.contentChange'; - export const abcEditorTypeCommand = '_abcEditor.type'; - - export interface CustomEditorContentChangeEvent { - readonly content: string; - readonly source: vscode.Uri; - } -} - -export class AbcTextEditorProvider implements vscode.CustomTextEditorProvider { - - public static readonly viewType = 'testWebviewEditor.abc'; - - private activeEditor?: AbcEditor; - - public constructor( - private readonly context: vscode.ExtensionContext, - ) { } - - public register(): vscode.Disposable { - const provider = vscode.window.registerCustomEditorProvider(AbcTextEditorProvider.viewType, this); - - const commands: vscode.Disposable[] = []; - commands.push(vscode.commands.registerCommand(Testing.abcEditorTypeCommand, (content: string) => { - this.activeEditor?.testing_fakeInput(content); - })); - - return vscode.Disposable.from(provider, ...commands); - } - - public async resolveCustomTextEditor(document: vscode.TextDocument, panel: vscode.WebviewPanel) { - const editor = new AbcEditor(document, this.context.extensionPath, panel); - - this.activeEditor = editor; - - panel.onDidChangeViewState(({ webviewPanel }) => { - if (this.activeEditor === editor && !webviewPanel.active) { - this.activeEditor = undefined; - } - if (webviewPanel.active) { - this.activeEditor = editor; - } - }); - } -} - -class AbcEditor extends Disposable { - - public readonly _onDispose = this._register(new vscode.EventEmitter()); - public readonly onDispose = this._onDispose.event; - - private readonly limit = pLimit(1); - private syncedVersion: number = -1; - private currentWorkspaceEdit?: Thenable; - - constructor( - private readonly document: vscode.TextDocument, - private readonly _extensionPath: string, - private readonly panel: vscode.WebviewPanel, - ) { - super(); - - panel.webview.options = { - enableScripts: true, - }; - panel.webview.html = this.html; - - this._register(vscode.workspace.onDidChangeTextDocument(e => { - if (e.document === this.document) { - this.update(); - } - })); - - this._register(panel.webview.onDidReceiveMessage(message => { - switch (message.type) { - case 'edit': - this.doEdit(message.value); - break; - - case 'didChangeContent': - vscode.commands.executeCommand(Testing.abcEditorContentChangeCommand, { - content: message.value, - source: document.uri, - } as Testing.CustomEditorContentChangeEvent); - break; - } - })); - - this._register(panel.onDidDispose(() => { this.dispose(); })); - - this.update(); - } - - public testing_fakeInput(value: string) { - this.panel.webview.postMessage({ - type: 'fakeInput', - value: value, - }); - } - - private async doEdit(value: string) { - const edit = new vscode.WorkspaceEdit(); - edit.replace(this.document.uri, this.document.validateRange(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(999999, 999999))), value); - this.limit(() => { - this.currentWorkspaceEdit = vscode.workspace.applyEdit(edit).then(() => { - this.syncedVersion = this.document.version; - this.currentWorkspaceEdit = undefined; - }); - return this.currentWorkspaceEdit; - }); - } - - public override dispose() { - if (this.isDisposed) { - return; - } - - this._onDispose.fire(); - super.dispose(); - } - - private get html() { - const contentRoot = path.join(this._extensionPath, 'customEditorMedia'); - const scriptUri = vscode.Uri.file(path.join(contentRoot, 'textEditor.js')); - const nonce = getNonce(); - return /* html */` - - - - - - Document - - - - - - `; - } - - public async update() { - await this.currentWorkspaceEdit; - - if (this.isDisposed || this.syncedVersion >= this.document.version) { - return; - } - - this.panel.webview.postMessage({ - type: 'setValue', - value: this.document.getText(), - }); - this.syncedVersion = this.document.version; - } -} - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/vscode-custom-editor-tests/src/dispose.ts b/extensions/vscode-custom-editor-tests/src/dispose.ts deleted file mode 100644 index 548094c28e5..00000000000 --- a/extensions/vscode-custom-editor-tests/src/dispose.ts +++ /dev/null @@ -1,42 +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 vscode from 'vscode'; - -export function disposeAll(disposables: vscode.Disposable[]) { - while (disposables.length) { - const item = disposables.pop(); - if (item) { - item.dispose(); - } - } -} - -export abstract class Disposable { - private _isDisposed = false; - - protected _disposables: vscode.Disposable[] = []; - - public dispose(): any { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - disposeAll(this._disposables); - } - - protected _register(value: T): T { - if (this._isDisposed) { - value.dispose(); - } else { - this._disposables.push(value); - } - return value; - } - - protected get isDisposed() { - return this._isDisposed; - } -} \ No newline at end of file diff --git a/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts b/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts deleted file mode 100644 index 7fc68f61c02..00000000000 --- a/extensions/vscode-custom-editor-tests/src/test/customEditor.test.ts +++ /dev/null @@ -1,316 +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 'mocha'; -import * as assert from 'assert'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Testing } from '../customTextEditor'; -import { closeAllEditors, delay, disposeAll, randomFilePath } from './utils'; - -assert.ok(vscode.workspace.rootPath); -const testWorkspaceRoot = vscode.Uri.file(path.join(vscode.workspace.rootPath!, 'customEditors')); - -const commands = Object.freeze({ - open: 'vscode.open', - openWith: 'vscode.openWith', - save: 'workbench.action.files.save', - undo: 'undo', -}); - -async function writeRandomFile(options: { ext: string; contents: string }): Promise { - const fakeFile = randomFilePath({ root: testWorkspaceRoot, ext: options.ext }); - await fs.promises.writeFile(fakeFile.fsPath, Buffer.from(options.contents)); - return fakeFile; -} - -const disposables: vscode.Disposable[] = []; -function _register(disposable: T) { - disposables.push(disposable); - return disposable; -} - -class CustomEditorUpdateListener { - - public static create() { - return _register(new CustomEditorUpdateListener()); - } - - private readonly commandSubscription: vscode.Disposable; - - private readonly unconsumedResponses: Array = []; - private readonly callbackQueue: Array<(data: Testing.CustomEditorContentChangeEvent) => void> = []; - - private constructor() { - this.commandSubscription = vscode.commands.registerCommand(Testing.abcEditorContentChangeCommand, (data: Testing.CustomEditorContentChangeEvent) => { - if (this.callbackQueue.length) { - const callback = this.callbackQueue.shift(); - assert.ok(callback); - callback!(data); - } else { - this.unconsumedResponses.push(data); - } - }); - } - - dispose() { - this.commandSubscription.dispose(); - } - - async nextResponse(): Promise { - if (this.unconsumedResponses.length) { - return this.unconsumedResponses.shift()!; - } - - return new Promise(resolve => { - this.callbackQueue.push(resolve); - }); - } -} - - -suite('CustomEditor tests', () => { - setup(async () => { - await closeAllEditors(); - await resetTestWorkspace(); - }); - - teardown(async () => { - await closeAllEditors(); - disposeAll(disposables); - await resetTestWorkspace(); - }); - - test('Should load basic content from disk', async () => { - const startingContent = `load, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - - const { content } = await listener.nextResponse(); - assert.strictEqual(content, startingContent); - }); - - test('Should support basic edits', async () => { - const startingContent = `basic edit, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const newContent = `basic edit test`; - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, newContent); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, newContent); - }); - - test('Should support single undo', async () => { - const startingContent = `single undo, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const newContent = `undo test`; - { - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, newContent); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, newContent); - } - await delay(100); - { - await vscode.commands.executeCommand(commands.undo); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, startingContent); - } - }); - - test('Should support multiple undo', async () => { - const startingContent = `multiple undo, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const count = 10; - - // Make edits - for (let i = 0; i < count; ++i) { - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, `${i}`); - const { content } = await listener.nextResponse(); - assert.strictEqual(`${i}`, content); - } - - // Then undo them in order - for (let i = count - 1; i; --i) { - await delay(100); - await vscode.commands.executeCommand(commands.undo); - const { content } = await listener.nextResponse(); - assert.strictEqual(`${i - 1}`, content); - } - - { - await delay(100); - await vscode.commands.executeCommand(commands.undo); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, startingContent); - } - }); - - test('Should update custom editor on file move', async () => { - const startingContent = `file move, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const newFileName = vscode.Uri.file(path.join(testWorkspaceRoot.fsPath, 'y.abc')); - - const edit = new vscode.WorkspaceEdit(); - edit.renameFile(testDocument, newFileName); - - await vscode.workspace.applyEdit(edit); - - const response = (await listener.nextResponse()); - assert.strictEqual(response.content, startingContent); - assert.strictEqual(response.source.toString(), newFileName.toString()); - }); - - test('Should support saving custom editors', async () => { - const startingContent = `save, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const newContent = `save, new`; - { - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, newContent); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, newContent); - } - { - await vscode.commands.executeCommand(commands.save); - const fileContent = (await fs.promises.readFile(testDocument.fsPath)).toString(); - assert.strictEqual(fileContent, newContent); - } - }); - - test('Should undo after saving custom editor', async () => { - const startingContent = `undo after save, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const newContent = `undo after save, new`; - { - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, newContent); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, newContent); - } - { - await vscode.commands.executeCommand(commands.save); - const fileContent = (await fs.promises.readFile(testDocument.fsPath)).toString(); - assert.strictEqual(fileContent, newContent); - } - await delay(100); - { - await vscode.commands.executeCommand(commands.undo); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, startingContent); - } - }); - - test.skip('Should support untitled custom editors', async () => { - const listener = CustomEditorUpdateListener.create(); - - const untitledFile = randomFilePath({ root: testWorkspaceRoot, ext: '.abc' }).with({ scheme: 'untitled' }); - - await vscode.commands.executeCommand(commands.open, untitledFile); - assert.strictEqual((await listener.nextResponse()).content, ''); - - await vscode.commands.executeCommand(Testing.abcEditorTypeCommand, `123`); - assert.strictEqual((await listener.nextResponse()).content, '123'); - - await vscode.commands.executeCommand(commands.save); - const content = await fs.promises.readFile(untitledFile.fsPath); - assert.strictEqual(content.toString(), '123'); - }); - - test.skip('When switching away from a non-default custom editors and then back, we should continue using the non-default editor', async () => { - const startingContent = `switch, init`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - { - await vscode.commands.executeCommand(commands.open, testDocument, { preview: false }); - const { content } = await listener.nextResponse(); - assert.strictEqual(content, startingContent.toString()); - const activeEditor = vscode.window.activeTextEditor; - assert.ok(!activeEditor); - } - - // Switch to non-default editor - await vscode.commands.executeCommand(commands.openWith, testDocument, 'default', { preview: false }); - assert.strictEqual(vscode.window.activeTextEditor!?.document.uri.toString(), testDocument.toString()); - - // Then open a new document (hiding existing one) - const otherFile = vscode.Uri.file(path.join(testWorkspaceRoot.fsPath, 'other.json')); - await vscode.commands.executeCommand(commands.open, otherFile); - assert.strictEqual(vscode.window.activeTextEditor!?.document.uri.toString(), otherFile.toString()); - - // And then back - await vscode.commands.executeCommand('workbench.action.navigateBack'); - await vscode.commands.executeCommand('workbench.action.navigateBack'); - - // Make sure we have the file on as text - assert.ok(vscode.window.activeTextEditor); - assert.strictEqual(vscode.window.activeTextEditor!?.document.uri.toString(), testDocument.toString()); - }); - - test('Should release the text document when the editor is closed', async () => { - const startingContent = `release document init,`; - const testDocument = await writeRandomFile({ ext: '.abc', contents: startingContent }); - - const listener = CustomEditorUpdateListener.create(); - - await vscode.commands.executeCommand(commands.open, testDocument); - await listener.nextResponse(); - - const doc = vscode.workspace.textDocuments.find(x => x.uri.toString() === testDocument.toString()); - assert.ok(doc); - assert.ok(!doc!.isClosed); - - await closeAllEditors(); - await delay(100); - assert.ok(doc!.isClosed); - }); -}); - -async function resetTestWorkspace() { - try { - await vscode.workspace.fs.delete(testWorkspaceRoot, { recursive: true }); - } catch { - // ok if file doesn't exist - } - await vscode.workspace.fs.createDirectory(testWorkspaceRoot); -} diff --git a/extensions/vscode-custom-editor-tests/src/test/utils.ts b/extensions/vscode-custom-editor-tests/src/test/utils.ts deleted file mode 100644 index 80d2eea16a4..00000000000 --- a/extensions/vscode-custom-editor-tests/src/test/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; - -export function randomFilePath(args: { root: vscode.Uri; ext: string }): vscode.Uri { - const fileName = rndName(); - return vscode.Uri.joinPath(args.root, fileName + args.ext); -} - -export function rndName() { - let name = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 10; i++) { - name += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return name; -} - -export function closeAllEditors(): Thenable { - return vscode.commands.executeCommand('workbench.action.closeAllEditors'); -} - -export function disposeAll(disposables: vscode.Disposable[]) { - vscode.Disposable.from(...disposables).dispose(); -} - -export function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/extensions/vscode-custom-editor-tests/test-workspace/index.abc b/extensions/vscode-custom-editor-tests/test-workspace/index.abc deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/extensions/vscode-custom-editor-tests/tsconfig.json b/extensions/vscode-custom-editor-tests/tsconfig.json deleted file mode 100644 index 7234fdfeb97..00000000000 --- a/extensions/vscode-custom-editor-tests/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out", - "types": [ - "node" - ] - }, - "include": [ - "src/**/*", - "../../src/vscode-dts/vscode.d.ts" - ] -} diff --git a/extensions/vscode-custom-editor-tests/yarn.lock b/extensions/vscode-custom-editor-tests/yarn.lock deleted file mode 100644 index 6d502cede51..00000000000 --- a/extensions/vscode-custom-editor-tests/yarn.lock +++ /dev/null @@ -1,32 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@types/mocha@^9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" - integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== - -"@types/node@16.x": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== - -"@types/p-limit@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/p-limit/-/p-limit-2.2.0.tgz#94a608e9b258a6c6156a13d1a14fd720dba70b97" - integrity sha512-fGFbybl1r0oE9mqgfc2EHHUin9ZL5rbQIexWI6jYRU1ADVn4I3LHzT+g/kpPpZsfp8PB94CQ655pfAjNF8LP6A== - dependencies: - p-limit "*" - -p-limit@*, p-limit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" - integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== - dependencies: - p-try "^2.0.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== diff --git a/extensions/yarn.lock b/extensions/yarn.lock index da2bcfbff02..79297d0869a 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -42,10 +42,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -typescript@4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" - integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== +typescript@^4.8.1-rc: + version "4.8.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.1-rc.tgz#2baff2b14b916f06a97effbfcf59e46bab93e48a" + integrity sha512-ZoXadPUeEe1XOZe6CHG/QHZ6IFeRjrfzkpraRi9HOpGH0UOG/WaUrKvtSwDFigG8GuDA4zsDQHEZyqhmlCIyEw== vscode-grammar-updater@^1.1.0: version "1.1.0" diff --git a/package.json b/package.json index 147e3476c6d..ad5d37dec8a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.69.0", - "distro": "c58f78790c58ab690d170012ad340f84cff8b438", + "version": "1.71.0", + "distro": "9bf07d89c0958f1739e9fec6eff52d91c15a6ecf", "author": { "name": "Microsoft Corporation" }, @@ -56,19 +56,18 @@ "minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", "core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci", - "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci" + "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", + "webview-generate-csp-hash": "npx github:apaatsio/csp-hash-from-html csp-hash ./src/vs/workbench/contrib/webview/browser/pre/index.html" }, "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", "@parcel/watcher": "2.0.5", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/ripgrep": "^1.14.2", "@vscode/sqlite3": "5.0.8", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.4.2", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", @@ -87,19 +86,19 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.58", - "xterm-addon-search": "0.9.0-beta.39", - "xterm-addon-serialize": "0.7.0-beta.12", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.37", - "xterm-headless": "4.19.0-beta.58", + "xterm": "5.0.0-beta.35", + "xterm-addon-canvas": "0.2.0-beta.17", + "xterm-addon-search": "0.10.0-beta.5", + "xterm-addon-serialize": "0.8.0-beta.5", + "xterm-addon-unicode11": "0.4.0-beta.5", + "xterm-addon-webgl": "0.13.0-beta.35", + "xterm-headless": "5.0.0-beta.5", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "devDependencies": { "7zip": "0.0.6", - "@playwright/test": "1.21.0", - "@types/applicationinsights": "0.20.0", + "@playwright/test": "1.24.2", "@types/cookie": "^0.3.3", "@types/copy-webpack-plugin": "^6.0.3", "@types/cssnano": "^4.0.0", @@ -126,8 +125,8 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", - "@vscode/telemetry-extractor": "^1.9.6", - "@vscode/test-web": "^0.0.22", + "@vscode/telemetry-extractor": "^1.9.8", + "@vscode/test-web": "^0.0.29", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", @@ -138,7 +137,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "18.3.2", + "electron": "19.0.12", "eslint": "8.7.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^39.3.2", @@ -171,11 +170,11 @@ "husky": "^0.13.1", "innosetup": "6.0.5", "is": "^3.1.0", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.2.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", "lazy.js": "^0.4.2", "merge-options": "^1.0.1", "mime": "^1.4.1", @@ -202,7 +201,7 @@ "style-loader": "^1.3.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220608", + "typescript": "^4.9.0-dev.20220811", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/product.json b/product.json index d16abf8de29..f15ac34c671 100644 --- a/product.json +++ b/product.json @@ -27,7 +27,7 @@ "licenseFileName": "LICENSE.txt", "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", "urlProtocol": "code-oss", - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/3c8520fab514b9f56070214496b26ff68d1b1cb5/out/vs/workbench/contrib/webview/browser/pre/", + "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/ef65ac1ba57f57f2a3961bfe94aa20481caca4c6/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ { "name": "ms-vscode.js-debug-companion", @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.68.0", + "version": "1.70.0", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", @@ -61,7 +61,7 @@ }, { "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.2", + "version": "1.0.3", "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", "metadata": { "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", diff --git a/remote/.yarnrc b/remote/.yarnrc index 290849a6e63..3a3fbdc3350 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,4 +1,4 @@ disturl "http://nodejs.org/dist" -target "16.13.2" +target "16.14.2" runtime "node" build_from_source "true" diff --git a/remote/package.json b/remote/package.json index 7d504fc5871..642aa711865 100644 --- a/remote/package.json +++ b/remote/package.json @@ -5,12 +5,10 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", "@parcel/watcher": "2.0.5", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/ripgrep": "^1.14.2", "@vscode/vscode-languagedetection": "1.0.21", - "applicationinsights": "1.4.2", "cookie": "^0.4.0", "graceful-fs": "4.2.8", "http-proxy-agent": "^2.1.0", @@ -26,12 +24,13 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.58", - "xterm-addon-search": "0.9.0-beta.39", - "xterm-addon-serialize": "0.7.0-beta.12", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.37", - "xterm-headless": "4.19.0-beta.58", + "xterm": "5.0.0-beta.35", + "xterm-addon-canvas": "0.2.0-beta.17", + "xterm-addon-search": "0.10.0-beta.5", + "xterm-addon-serialize": "0.8.0-beta.5", + "xterm-addon-unicode11": "0.4.0-beta.5", + "xterm-addon-webgl": "0.13.0-beta.35", + "xterm-headless": "5.0.0-beta.5", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 07c0ff4786c..4f8837adba3 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -3,18 +3,18 @@ "version": "0.0.0", "private": true, "dependencies": { - "@microsoft/1ds-core-js": "^3.2.2", - "@microsoft/1ds-post-js": "^3.2.2", - "@microsoft/applicationinsights-web": "^2.8.4", + "@microsoft/1ds-core-js": "^3.2.2", + "@microsoft/1ds-post-js": "^3.2.2", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", "jschardet": "3.0.0", "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.58", - "xterm-addon-search": "0.9.0-beta.39", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.37" + "xterm": "5.0.0-beta.35", + "xterm-addon-canvas": "0.2.0-beta.17", + "xterm-addon-search": "0.10.0-beta.5", + "xterm-addon-unicode11": "0.4.0-beta.5", + "xterm-addon-webgl": "0.13.0-beta.35" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 5c008a2dbf0..035f8a6a240 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -20,35 +20,6 @@ "@microsoft/applicationinsights-shims" "^2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-analytics-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.8.4.tgz#3b32d8a2122be5d5993c74ef3217ebbf4876ea69" - integrity sha512-n/FPs8SS6rB8h+u157fiRh0TwUWKctxGNvr4M+LKeSdgDvf9c759gUeMR7r8xF6kBBfgkbmyaVORjsA1WJsU4g== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-channel-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.4.tgz#6de7210d87e61c72d3a9a06cbaeae14e1b543484" - integrity sha512-aml49Jya8LxX4tvyBbIvcxSo7UGI0k3HeiJQRFLeO+QlA+Ocsl10PqphU/OYJ4hh/P5/2QhEAq5bBM/b9/PNrg== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-common@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.4.tgz#45b422cf1804df06d5abb2ceda5ed65268a92135" - integrity sha512-uDvd4zxNGNYFE0TF4h7tAg+eMIPatyd1QdkP8fA4UYwshF4/+UwS1wegjXLEWQRRH87+UAyvx4IKQjobzzEX0A== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-core-js@2.8.4": version "2.8.4" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" @@ -57,45 +28,11 @@ "@microsoft/applicationinsights-shims" "2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-dependencies-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.8.4.tgz#b9afbb81fb44aeb3033ecff1d2e4d33d71c1d41c" - integrity sha512-dr11EBFBR+vmtTipubZv9KSWRXLk6XdutkEgilgzXdSFun0dqR+ZSHEmMWyqE8ZsJtW+1HzdKuGtODSQY6uHyw== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-properties-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.8.4.tgz#c52a6ce03b8f99b2110a097b4ef30686dfb433f8" - integrity sha512-UI0afK5e8yUJ1qIdy+7FA/G9TB+st0++trx4bUMa+Hb6gJggdQPq94lBFJL0yzo4QsgQwozVwkInXy4534tTYQ== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== -"@microsoft/applicationinsights-web@^2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.8.4.tgz#6b385d385790b9574dad0754b860e656cde470b7" - integrity sha512-3CtZiM6e5Q0AA+1NE4k8A0+Y0FE1jsK4u0sb4AkvV7b4cwb86I9l7F7fQPU+V/ltkni0g2WtDrMNU93RuxSmNw== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.8.4" - "@microsoft/applicationinsights-channel-js" "2.8.4" - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-dependencies-js" "2.8.4" - "@microsoft/applicationinsights-properties-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/dynamicproto-js@^1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" @@ -131,22 +68,27 @@ vscode-textmate@7.0.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== -xterm-addon-search@0.9.0-beta.39: - version "0.9.0-beta.39" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.39.tgz#e8376e1485ee7d763c07d1a8f1354114f65b3e3e" - integrity sha512-h45wkecgfqXXoAUqgNytAfSd6g0xNT6rZy/enVaEU0aes7QoL9pxHUKkCry8PP6hs03Slk0VxQ4AGsbSZGvK/w== +xterm-addon-canvas@0.2.0-beta.17: + version "0.2.0-beta.17" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.17.tgz#e84a86530b20bd3edcce16b4566f346cd186ab4d" + integrity sha512-2ukPdCA92VTFYQRE56ylzvI3cfaQYDWd/Mc4jlEItI6sV/EA5RnUbbP+2sFIx0JlmHK6nVYXXNY2p6QRB7MRew== -xterm-addon-unicode11@0.4.0-beta.3: - version "0.4.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" - integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== +xterm-addon-search@0.10.0-beta.5: + version "0.10.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.5.tgz#a2cb16bda4ddf8783b80433155ad94f5822271f8" + integrity sha512-kjog7cm1iEZ2XyQFVs3KAvoI2pKoX0cq2WWjL0FuXYXpKQ9vXmfrWSR7PiJ6zpTIRvr6UtaSGKhmZVHLNA79WA== -xterm-addon-webgl@0.12.0-beta.37: - version "0.12.0-beta.37" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.37.tgz#e465100041a7e0b1d32b01cd0b0ba5a516ac13c2" - integrity sha512-d4GfKlMrWZGzsMfMHd2siG+QiOvwikhwiu+JQlIhlnAShT/wU5BOGbNfDDA1tHmkW7G1UCFgucBuVxrs5wjuBQ== +xterm-addon-unicode11@0.4.0-beta.5: + version "0.4.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.5.tgz#3900e66f10d2e506133b61d7421aab6878d32665" + integrity sha512-+g+fuxAd/tkCEJ/jhdnebXKtdPrhsu4VKWNnB/3qM35GbuGQOasmYFYnKL+HYZMpbQ6YqeZcXTVg/wnCTttz0g== -xterm@4.19.0-beta.58: - version "4.19.0-beta.58" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.58.tgz#cbe535b0b85bc7f8ae3cc242df006bea04cdf541" - integrity sha512-fr3QC2qS2NBab8X7kwA/3QZELzQqhAKsngvK8NkhsrFN1DAuyEETkzqXuKbO7OKpCB6VqGDN6GPkNlUB9VrIDA== +xterm-addon-webgl@0.13.0-beta.35: + version "0.13.0-beta.35" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.35.tgz#98b5dc102583120de25c7c6e0f35cd34ab6bef98" + integrity sha512-powgeb3ifEZK7zWwLJuE8YRz/Z0UzDrrVA9NTx9mH7+vWxrGt9ZgroychoeCqZBvKMofiUM5vlf1oWdSmsMfzw== + +xterm@5.0.0-beta.35: + version "5.0.0-beta.35" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.35.tgz#3bbf5780c98aaaa6476e7590dff1d8f5014fce9d" + integrity sha512-tzJTel1E/FmB0VQDb44xApVB+ln+BifIbHD4V9x9Z3D9m1cMmPHy8J8efdILkn1TxXGlCl7VC35nG122Qq5exw== diff --git a/remote/yarn.lock b/remote/yarn.lock index 0094a657cc7..6b9db5b2083 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -20,35 +20,6 @@ "@microsoft/applicationinsights-shims" "^2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-analytics-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.8.4.tgz#3b32d8a2122be5d5993c74ef3217ebbf4876ea69" - integrity sha512-n/FPs8SS6rB8h+u157fiRh0TwUWKctxGNvr4M+LKeSdgDvf9c759gUeMR7r8xF6kBBfgkbmyaVORjsA1WJsU4g== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-channel-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.4.tgz#6de7210d87e61c72d3a9a06cbaeae14e1b543484" - integrity sha512-aml49Jya8LxX4tvyBbIvcxSo7UGI0k3HeiJQRFLeO+QlA+Ocsl10PqphU/OYJ4hh/P5/2QhEAq5bBM/b9/PNrg== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-common@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.4.tgz#45b422cf1804df06d5abb2ceda5ed65268a92135" - integrity sha512-uDvd4zxNGNYFE0TF4h7tAg+eMIPatyd1QdkP8fA4UYwshF4/+UwS1wegjXLEWQRRH87+UAyvx4IKQjobzzEX0A== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-core-js@2.8.4": version "2.8.4" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" @@ -57,45 +28,11 @@ "@microsoft/applicationinsights-shims" "2.0.1" "@microsoft/dynamicproto-js" "^1.1.6" -"@microsoft/applicationinsights-dependencies-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.8.4.tgz#b9afbb81fb44aeb3033ecff1d2e4d33d71c1d41c" - integrity sha512-dr11EBFBR+vmtTipubZv9KSWRXLk6XdutkEgilgzXdSFun0dqR+ZSHEmMWyqE8ZsJtW+1HzdKuGtODSQY6uHyw== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-properties-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.8.4.tgz#c52a6ce03b8f99b2110a097b4ef30686dfb433f8" - integrity sha512-UI0afK5e8yUJ1qIdy+7FA/G9TB+st0++trx4bUMa+Hb6gJggdQPq94lBFJL0yzo4QsgQwozVwkInXy4534tTYQ== - dependencies: - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== -"@microsoft/applicationinsights-web@^2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.8.4.tgz#6b385d385790b9574dad0754b860e656cde470b7" - integrity sha512-3CtZiM6e5Q0AA+1NE4k8A0+Y0FE1jsK4u0sb4AkvV7b4cwb86I9l7F7fQPU+V/ltkni0g2WtDrMNU93RuxSmNw== - dependencies: - "@microsoft/applicationinsights-analytics-js" "2.8.4" - "@microsoft/applicationinsights-channel-js" "2.8.4" - "@microsoft/applicationinsights-common" "2.8.4" - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-dependencies-js" "2.8.4" - "@microsoft/applicationinsights-properties-js" "2.8.4" - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - "@microsoft/dynamicproto-js@^1.1.6": version "1.1.6" resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" @@ -168,16 +105,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -applicationinsights@1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.2.tgz#2f25f7a3f3e5bf0ab4486b63e42a48a9ec321d52" - integrity sha512-1wE37G9zEMZTsPJVQ8BDrQtsGgG3DGMActLHwPAF8TYHAXkfqqpeZYCH0XV4lUZ7H4MffRMwN2Ln2nEtUmT8HQ== - dependencies: - cls-hooked "^4.2.2" - continuation-local-storage "^3.2.1" - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "^0.3.3" - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -191,21 +118,6 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -async-hook-jl@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" - integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== - dependencies: - stack-chain "^1.3.7" - -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -245,15 +157,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -cls-hooked@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" - integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== - dependencies: - async-hook-jl "^1.7.6" - emitter-listener "^1.0.1" - semver "^5.4.1" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -264,14 +167,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - cookie@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" @@ -325,25 +220,6 @@ detect-libc@^2.0.0: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== -diagnostic-channel-publishers@^0.3.3: - version "0.3.5" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536" - integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ== - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - integrity sha512-awkcaaNNi0RfUGJf7r2+K4oJs1OyiIG2m/Jwvyi0OeQxdw+UU/iwbiejTPa3tUeyXtBcp2fef0JOJNdD62r/zg== - dependencies: - semver "^5.3.0" - -emitter-listener@^1.0.1, emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -706,16 +582,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -semver@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@^5.4.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -728,11 +594,6 @@ set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - signal-exit@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" @@ -783,11 +644,6 @@ spdlog@^0.13.0: mkdirp "^0.5.5" nan "^2.14.0" -stack-chain@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -932,35 +788,40 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.9.0-beta.39: - version "0.9.0-beta.39" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.39.tgz#e8376e1485ee7d763c07d1a8f1354114f65b3e3e" - integrity sha512-h45wkecgfqXXoAUqgNytAfSd6g0xNT6rZy/enVaEU0aes7QoL9pxHUKkCry8PP6hs03Slk0VxQ4AGsbSZGvK/w== +xterm-addon-canvas@0.2.0-beta.17: + version "0.2.0-beta.17" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.17.tgz#e84a86530b20bd3edcce16b4566f346cd186ab4d" + integrity sha512-2ukPdCA92VTFYQRE56ylzvI3cfaQYDWd/Mc4jlEItI6sV/EA5RnUbbP+2sFIx0JlmHK6nVYXXNY2p6QRB7MRew== -xterm-addon-serialize@0.7.0-beta.12: - version "0.7.0-beta.12" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.12.tgz#4f845d8b1a9f9b7ae3f910455ce8c58b041babc7" - integrity sha512-b4Ug0B/RSJMux+KAcp+PXVqubVyXjN1yCQw1FOkgVYTpmd9AH/X+EcxKml5Lz8DsKmsXqfD9AlV3WpEeT+OtMw== +xterm-addon-search@0.10.0-beta.5: + version "0.10.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.5.tgz#a2cb16bda4ddf8783b80433155ad94f5822271f8" + integrity sha512-kjog7cm1iEZ2XyQFVs3KAvoI2pKoX0cq2WWjL0FuXYXpKQ9vXmfrWSR7PiJ6zpTIRvr6UtaSGKhmZVHLNA79WA== -xterm-addon-unicode11@0.4.0-beta.3: - version "0.4.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" - integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== +xterm-addon-serialize@0.8.0-beta.5: + version "0.8.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.5.tgz#3d2f3be173f4f1c31ae7bf25179e8ecddf2b33b2" + integrity sha512-rkSUaO1XBcy3ipScZMA5PrOKu/DfXo8XC/V7hZlhMiBNbZKlbk2rFb3X0FB1f07hw7oEkHLjuIJEY5Qtxfe9/w== -xterm-addon-webgl@0.12.0-beta.37: - version "0.12.0-beta.37" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.37.tgz#e465100041a7e0b1d32b01cd0b0ba5a516ac13c2" - integrity sha512-d4GfKlMrWZGzsMfMHd2siG+QiOvwikhwiu+JQlIhlnAShT/wU5BOGbNfDDA1tHmkW7G1UCFgucBuVxrs5wjuBQ== +xterm-addon-unicode11@0.4.0-beta.5: + version "0.4.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.5.tgz#3900e66f10d2e506133b61d7421aab6878d32665" + integrity sha512-+g+fuxAd/tkCEJ/jhdnebXKtdPrhsu4VKWNnB/3qM35GbuGQOasmYFYnKL+HYZMpbQ6YqeZcXTVg/wnCTttz0g== -xterm-headless@4.19.0-beta.58: - version "4.19.0-beta.58" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.58.tgz#3a38cf3b9cd2606fb342a5cf8f2a0cfb963a62e8" - integrity sha512-wKQW8VzkFjyYDvcaM26gulo+YghyocfkZnKMH7gc/+/mFn3YXUPBPuOcX6e0M7NibyMlewpQpZjHQUPLBjkzfw== +xterm-addon-webgl@0.13.0-beta.35: + version "0.13.0-beta.35" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.35.tgz#98b5dc102583120de25c7c6e0f35cd34ab6bef98" + integrity sha512-powgeb3ifEZK7zWwLJuE8YRz/Z0UzDrrVA9NTx9mH7+vWxrGt9ZgroychoeCqZBvKMofiUM5vlf1oWdSmsMfzw== -xterm@4.19.0-beta.58: - version "4.19.0-beta.58" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.58.tgz#cbe535b0b85bc7f8ae3cc242df006bea04cdf541" - integrity sha512-fr3QC2qS2NBab8X7kwA/3QZELzQqhAKsngvK8NkhsrFN1DAuyEETkzqXuKbO7OKpCB6VqGDN6GPkNlUB9VrIDA== +xterm-headless@5.0.0-beta.5: + version "5.0.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.0.0-beta.5.tgz#e29b6c5081f31f887122b7263ba996b0c46b3c22" + integrity sha512-CMQ1+prBNF92oBMeZzc2rfTcmOaCGfwwSaoPYNTjyziZT6mZsEg7amajYkb0YAnqJ29MFm4kPGZbU78/dX4k2A== + +xterm@5.0.0-beta.35: + version "5.0.0-beta.35" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.35.tgz#3bbf5780c98aaaa6476e7590dff1d8f5014fce9d" + integrity sha512-tzJTel1E/FmB0VQDb44xApVB+ln+BifIbHD4V9x9Z3D9m1cMmPHy8J8efdILkn1TxXGlCl7VC35nG122Qq5exw== yallist@^4.0.0: version "4.0.0" diff --git a/resources/linux/debian/control.template b/resources/linux/debian/control.template index bfe0aa52da2..1a5981bb219 100644 --- a/resources/linux/debian/control.template +++ b/resources/linux/debian/control.template @@ -1,7 +1,8 @@ Package: @@NAME@@ Version: @@VERSION@@ Section: devel -Depends: libnss3 (>= 2:3.26), gnupg, apt, libxkbfile1, libsecret-1-0, libgtk-3-0 (>= 3.10.0), libxss1, libgbm1 +Depends: @@DEPENDS@@ +Recommends: @@RECOMMENDS@@ Priority: optional Architecture: @@ARCHITECTURE@@ Maintainer: Microsoft Corporation diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index 5b7eadb8c9b..61659d25837 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -11,6 +11,8 @@ Icon: @@NAME@@.xpm Requires: @@DEPENDENCIES@@ AutoReq: 0 +%global __provides_exclude_from ^%{_datadir}/%{name}/.*\\.so.*$ + %description Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle. See https://code.visualstudio.com/docs/setup/linux for installation instructions and FAQ. @@ -19,29 +21,32 @@ Visual Studio Code is a new choice of tool that combines the simplicity of a cod %define _build_id_links none %install -mkdir -p %{buildroot}/usr/share/@@NAME@@ -mkdir -p %{buildroot}/usr/share/applications -mkdir -p %{buildroot}/usr/share/pixmaps -mkdir -p %{buildroot}/usr/share/bash-completion/completions -mkdir -p %{buildroot}/usr/share/zsh/site-functions -mkdir -p %{buildroot}/usr/share/mime/packages -cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ -cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications -cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications -cp -r usr/share/mime/packages/@@NAME@@-workspace.xml %{buildroot}/usr/share/mime/packages/@@NAME@@-workspace.xml -cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps -cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ -cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ +# Destination directories +mkdir -p %{buildroot}%{_bindir} +mkdir -p %{buildroot}%{_datadir}/%{name} +mkdir -p %{buildroot}%{_datadir}/applications +mkdir -p %{buildroot}%{_datadir}/mime/packages +mkdir -p %{buildroot}%{_datadir}/pixmaps +mkdir -p %{buildroot}%{_datadir}/bash-completion/completions +mkdir -p %{buildroot}%{_datadir}/zsh/site-functions +# Application +cp -r usr/share/%{name}/* %{buildroot}%{_datadir}/%{name} +ln -s %{_datadir}/%{name}/bin/%{name} %{buildroot}%{_bindir}/%{name} +# Support files +cp -r usr/share/applications/%{name}.desktop %{buildroot}%{_datadir}/applications +cp -r usr/share/applications/%{name}-url-handler.desktop %{buildroot}%{_datadir}/applications +cp -r usr/share/mime/packages/%{name}-workspace.xml %{buildroot}%{_datadir}/mime/packages/%{name}-workspace.xml +cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}%{_datadir}/pixmaps +# Shell completions +cp usr/share/bash-completion/completions/%{name} %{buildroot}%{_datadir}/bash-completion/completions/%{name} +cp usr/share/zsh/site-functions/_%{name} %{buildroot}%{_datadir}/zsh/site-functions/_%{name} %post # Remove the legacy bin command if this is the stable build -if [ "@@NAME@@" = "code" ]; then +if [ "%{name}" = "code" ]; then rm -f /usr/local/bin/code fi -# Symlink bin command to /usr/bin -ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@ - # Register yum repository # TODO: #229: Enable once the yum repository is signed #if [ "@@NAME@@" != "code-oss" ]; then @@ -53,24 +58,21 @@ ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@ #fi # Update mimetype database to pickup workspace mimetype -update-mime-database /usr/share/mime &> /dev/null || : +update-mime-database %{_datadir}/mime &> /dev/null || : %postun -if [ $1 = 0 ]; then - rm -f /usr/bin/@@NAME@@ -fi - # Update mimetype database for removed workspace mimetype -update-mime-database /usr/share/mime &> /dev/null || : +update-mime-database %{_datadir}/mime &> /dev/null || : %files %defattr(-,root,root) -%attr(4755, root, root) /usr/share/@@NAME@@/chrome-sandbox +%attr(4755, root, root) %{_datadir}/%{name}/chrome-sandbox -/usr/share/@@NAME@@/ -/usr/share/applications/@@NAME@@.desktop -/usr/share/applications/@@NAME@@-url-handler.desktop -/usr/share/mime/packages/@@NAME@@-workspace.xml -/usr/share/pixmaps/@@ICON@@.png -/usr/share/bash-completion/completions/@@NAME@@ -/usr/share/zsh/site-functions/_@@NAME@@ +%{_bindir}/%{name} +%{_datadir}/%{name}/ +%{_datadir}/applications/%{name}.desktop +%{_datadir}/applications/%{name}-url-handler.desktop +%{_datadir}/mime/packages/%{name}-workspace.xml +%{_datadir}/pixmaps/@@ICON@@.png +%{_datadir}/bash-completion/completions/%{name} +%{_datadir}/zsh/site-functions/_%{name} diff --git a/resources/linux/snap/snapcraft.yaml b/resources/linux/snap/snapcraft.yaml index 8bbad497ace..fc775b6554f 100644 --- a/resources/linux/snap/snapcraft.yaml +++ b/resources/linux/snap/snapcraft.yaml @@ -58,11 +58,9 @@ apps: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --no-sandbox common-id: @@NAME@@.desktop environment: - DISABLE_WAYLAND: 1 GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas url-handler: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url --no-sandbox environment: - DISABLE_WAYLAND: 1 GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 8ab9e7d76dd..d5b0ff422b9 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -25,7 +25,6 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: compile-extension:vscode-colorize-tests^ :: compile-extension:markdown-language-features^ :: compile-extension:typescript-language-features^ - :: compile-extension:vscode-custom-editor-tests^ :: compile-extension:vscode-notebook-tests^ :: compile-extension:emmet^ :: compile-extension:css-language-features-server^ @@ -33,6 +32,7 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: compile-extension:json-language-features-server^ :: compile-extension:git^ :: compile-extension:ipynb^ + :: compile-extension:configuration-editing^ :: compile-extension-media :: Configuration for more verbose output @@ -102,6 +102,12 @@ mkdir %IPYNBWORKSPACE% call "%INTEGRATION_TEST_ELECTRON_PATH%" %IPYNBWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\ipynb --extensionTestsPath=%~dp0\..\extensions\ipynb\out\test %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% +mkdir %CFWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %CFWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\configuration-editing --extensionTestsPath=%~dp0\..\extensions\configuration-editing\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% :: Tests standalone (CommonJS) diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index e381f61b40a..056e536f46a 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -32,7 +32,6 @@ else # and the build bundles extensions into .build webpacked # yarn gulp compile-extension:vscode-api-tests \ # compile-extension:vscode-colorize-tests \ - # compile-extension:vscode-custom-editor-tests \ # compile-extension:vscode-notebook-tests \ # compile-extension:markdown-language-features \ # compile-extension:typescript-language-features \ @@ -42,6 +41,7 @@ else # compile-extension:json-language-features-server \ # compile-extension:git \ # compile-extension:ipynb \ + # compile-extension:configuration-editing \ # compile-extension-media # Configuration for more verbose output @@ -120,6 +120,12 @@ echo "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test $API_TESTS_EXTRA_ARGS kill_app +echo +echo "### Configuration editing tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/configuration-editing --extensionTestsPath=$ROOT/extensions/configuration-editing/out/test $API_TESTS_EXTRA_ARGS +kill_app + # Tests standalone (CommonJS) diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index 7decdf3798f..6d883f53217 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -54,6 +54,7 @@ else # compile-extension:emmet \ # compile-extension:git \ # compile-extension:ipynb \ + # compile-extension:configuration-editing \ # compile-extension:microsoft-authentication \ # compile-extension:github-authentication \ # compile-extension-media @@ -132,6 +133,11 @@ echo "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS --folder-uri=$AUTHORITY$(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$REMOTE_VSCODE/ipynb --extensionTestsPath=$REMOTE_VSCODE/ipynb/out/test $API_TESTS_EXTRA_ARGS $EXTRA_INTEGRATION_TEST_ARGUMENTS kill_app +echo +echo "### Configuration editing tests" +echo +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS --folder-uri=$AUTHORITY$(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$REMOTE_VSCODE/configuration-editing --extensionTestsPath=$REMOTE_VSCODE/configuration-editing/out/test $API_TESTS_EXTRA_ARGS $EXTRA_INTEGRATION_TEST_ARGUMENTS +kill_app # Cleanup diff --git a/scripts/test-web-integration.bat b/scripts/test-web-integration.bat index c5b89b85b36..7be27c00b31 100644 --- a/scripts/test-web-integration.bat +++ b/scripts/test-web-integration.bat @@ -29,6 +29,7 @@ if "%VSCODE_REMOTE_SERVER_PATH%"=="" ( :: compile-extension:markdown-language-features^ :: compile-extension:typescript-language-features^ :: compile-extension:emmet^ + :: compile-extension:configuration-editing^ :: compile-extension:git^ :: compile-extension-media ) @@ -70,3 +71,10 @@ set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %GITWORKSPACE% call node .\test\integration\browser\out\index.js --workspacePath=%GITWORKSPACE% --extensionDevelopmentPath=.\extensions\git --extensionTestsPath=.\extensions\git\out\test %* if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %CFWORKSPACE% +call node .\test\integration\browser\out\index.js --workspacePath=%CFWORKSPACE% --extensionDevelopmentPath=.\extensions\configuration-editing --extensionTestsPath=.\extensions\configuration-editing\out\test %* +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-web-integration.sh b/scripts/test-web-integration.sh index 4246cdc6ac1..95278eec0e3 100755 --- a/scripts/test-web-integration.sh +++ b/scripts/test-web-integration.sh @@ -25,6 +25,7 @@ else # compile-extension:emmet \ # compile-extension:git \ # compile-extension:ipynb \ + # compile-extension:configuration-editing \ # compile-extension-media fi @@ -70,3 +71,8 @@ echo "### Ipynb tests" echo node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test "$@" +echo +echo "### Configuration editing tests" +echo +node test/integration/browser/out/index.js --workspacePath $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/configuration-editing --extensionTestsPath=$ROOT/extensions/configuration-editing/out/test "$@" + diff --git a/scripts/update-xterm.js b/scripts/update-xterm.js index 70637a8493c..bc574935902 100644 --- a/scripts/update-xterm.js +++ b/scripts/update-xterm.js @@ -8,6 +8,7 @@ const path = require('path'); const moduleNames = [ 'xterm', + 'xterm-addon-canvas', 'xterm-addon-search', 'xterm-addon-unicode11', 'xterm-addon-webgl' @@ -30,7 +31,17 @@ function getLatestModuleVersion(moduleName) { if (err) { reject(err); } - const versions = JSON.parse(stdout); + let versions = JSON.parse(stdout); + // HACK: Some bad versions were published as v5 which cannot be unpublished, ignore these + if (moduleName === 'xterm-addon-canvas') { + versions = versions.filter(e => ![ + '0.12.0', + '5.0.0-beta.1', + '5.0.0-beta.2', + '5.0.0-beta.3', + '5.0.0-beta.4', + ].includes(e)); + } resolve(versions[versions.length - 1]); }); }); diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index c035cb6f262..10db71ff3eb 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -88,15 +88,6 @@ function pipeLoggingToParent() { } } - // Add the stack trace as payload if we are told so. We remove the message and the 2 top frames - // to start the stacktrace where the console message was being written - if (process.env['VSCODE_LOG_STACK'] === 'true') { - const stack = new Error().stack; - if (stack) { - argsArray.push({ __$stack: stack.split('\n').slice(3).join('\n') }); - } - } - try { const res = JSON.stringify(argsArray, function (key, value) { @@ -155,13 +146,8 @@ function pipeLoggingToParent() { safeSend({ type: '__$console', severity, arguments: args }); } - let isMakingConsoleCall = false; - /** - * Wraps a console message so that it is transmitted to the renderer. If - * native logging is turned on, the original console message will be written - * as well. This is needed since the console methods are "magic" in V8 and - * are the only methods that allow later introspection of logged variables. + * Wraps a console message so that it is transmitted to the renderer. * * The wrapped property is not defined with `writable: false` to avoid * throwing errors, but rather a no-op setting. See https://github.com/microsoft/vscode-extension-telemetry/issues/88 @@ -170,26 +156,10 @@ function pipeLoggingToParent() { * @param {'log' | 'warn' | 'error'} severity */ function wrapConsoleMethod(method, severity) { - if (process.env['VSCODE_LOG_NATIVE'] === 'true') { - const original = console[method]; - const stream = method === 'error' || method === 'warn' ? process.stderr : process.stdout; - Object.defineProperty(console, method, { - set: () => { }, - get: () => function () { - safeSendConsoleMessage(severity, safeToArray(arguments)); - isMakingConsoleCall = true; - stream.write('\nSTART_NATIVE_LOG\n'); - original.apply(console, arguments); - stream.write('\nEND_NATIVE_LOG\n'); - isMakingConsoleCall = false; - }, - }); - } else { - Object.defineProperty(console, method, { - set: () => { }, - get: () => function () { safeSendConsoleMessage(severity, safeToArray(arguments)); }, - }); - } + Object.defineProperty(console, method, { + set: () => { }, + get: () => function () { safeSendConsoleMessage(severity, safeToArray(arguments)); }, + }); } /** @@ -211,13 +181,11 @@ function pipeLoggingToParent() { Object.defineProperty(stream, 'write', { set: () => { }, get: () => (chunk, encoding, callback) => { - if (!isMakingConsoleCall) { - buf += chunk.toString(encoding); - const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf('\n'); - if (eol !== -1) { - console[severity](buf.slice(0, eol)); - buf = buf.slice(eol + 1); - } + buf += chunk.toString(encoding); + const eol = buf.length > MAX_STREAM_BUFFER_LENGTH ? buf.length : buf.lastIndexOf('\n'); + if (eol !== -1) { + console[severity](buf.slice(0, eol)); + buf = buf.slice(eol + 1); } original.call(stream, chunk, encoding, callback); @@ -231,7 +199,7 @@ function pipeLoggingToParent() { wrapConsoleMethod('log', 'log'); wrapConsoleMethod('warn', 'warn'); wrapConsoleMethod('error', 'error'); - } else if (process.env['VSCODE_LOG_NATIVE'] !== 'true') { + } else { console.log = function () { /* ignore */ }; console.warn = function () { /* ignore */ }; console.info = function () { /* ignore */ }; diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 79381017f50..61ca6dd8477 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -136,6 +136,7 @@ 'vscode-textmate': `${baseNodeModulesPath}/vscode-textmate/release/main.js`, 'vscode-oniguruma': `${baseNodeModulesPath}/vscode-oniguruma/release/main.js`, 'xterm': `${baseNodeModulesPath}/xterm/lib/xterm.js`, + 'xterm-addon-canvas': `${baseNodeModulesPath}/xterm-addon-canvas/lib/xterm-addon-canvas.js`, 'xterm-addon-search': `${baseNodeModulesPath}/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-unicode11': `${baseNodeModulesPath}/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, 'xterm-addon-webgl': `${baseNodeModulesPath}/xterm-addon-webgl/lib/xterm-addon-webgl.js`, @@ -150,7 +151,7 @@ // which has a fallback to using node.js `require` // (node.js enabled renderers only) if (!safeProcess.sandboxed) { - loaderConfig.amdModulesPattern = /(^vs\/)|(^vscode-textmate$)|(^vscode-oniguruma$)|(^xterm$)|(^xterm-addon-search$)|(^xterm-addon-unicode11$)|(^xterm-addon-webgl$)|(^@vscode\/iconv-lite-umd$)|(^jschardet$)|(^@vscode\/vscode-languagedetection$)|(^vscode-regexp-languagedetection$)|(^tas-client-umd$)/; + loaderConfig.amdModulesPattern = /(^vs\/)|(^vscode-textmate$)|(^vscode-oniguruma$)|(^xterm$)|(^xterm-addon-canvas$)|(^xterm-addon-search$)|(^xterm-addon-unicode11$)|(^xterm-addon-webgl$)|(^@vscode\/iconv-lite-umd$)|(^jschardet$)|(^@vscode\/vscode-languagedetection$)|(^vscode-regexp-languagedetection$)|(^tas-client-umd$)/; } // Signal before require.config() diff --git a/src/bootstrap.js b/src/bootstrap.js index 319ddba9b02..a8970d05c4a 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -205,7 +205,7 @@ } /** - * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process} + * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process | undefined} */ function safeProcess() { const sandboxGlobals = safeSandboxGlobals(); diff --git a/src/buildfile.js b/src/buildfile.js index 6b49aa30083..b63d85617c3 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -32,12 +32,17 @@ exports.base = [ { name: 'vs/editor/common/services/editorSimpleWorker', include: ['vs/base/common/worker/simpleWorker'], - prepend: ['vs/loader.js', 'vs/nls.js'], - append: ['vs/base/worker/workerMain'], + exclude: ['vs/nls'], + prepend: [ + { path: 'vs/loader.js' }, + { path: 'vs/nls.js', amdModuleId: 'vs/nls' }, + { path: 'vs/base/worker/workerMain.js' } + ], dest: 'vs/base/worker/workerMain.js' }, { name: 'vs/base/common/worker/simpleWorker', + exclude: ['vs/nls'], } ]; diff --git a/src/main.js b/src/main.js index c9347d4516b..41c50d9518b 100644 --- a/src/main.js +++ b/src/main.js @@ -153,9 +153,6 @@ function configureCommandlineSwitchesSync(cliArgs) { // alias from us for --disable-gpu 'disable-hardware-acceleration', - // provided by Electron - 'disable-color-correct-rendering', - // override for the color profile to use 'force-color-profile' ]; @@ -221,6 +218,12 @@ function configureCommandlineSwitchesSync(cliArgs) { } }); + /* Following features are disabled from the runtime. + * `CalculateNativeWinOcclusion` - Disable native window occlusion tracker, + * Refs https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ + */ + app.commandLine.appendSwitch('disable-features', 'CalculateNativeWinOcclusion'); + // Support JS Flags const jsFlags = getJSFlags(cliArgs); if (jsFlags) { @@ -247,9 +250,7 @@ function readArgvConfigSync() { // Fallback to default if (!argvConfig) { - argvConfig = { - 'disable-color-correct-rendering': true // Force pre-Chrome-60 color profile handling (for https://github.com/microsoft/vscode/issues/51791) - }; + argvConfig = {}; } return argvConfig; @@ -279,11 +280,7 @@ function createDefaultArgvConfigSync(argvConfigPath) { '{', ' // Use software rendering instead of hardware accelerated rendering.', ' // This can help in cases where you see rendering issues in VS Code.', - ' // "disable-hardware-acceleration": true,', - '', - ' // Enabled by default by VS Code to resolve color issues in the renderer', - ' // See https://github.com/microsoft/vscode/issues/51791 for details', - ' "disable-color-correct-rendering": true', + ' // "disable-hardware-acceleration": true', '}' ]; @@ -322,7 +319,7 @@ function configureCrashReporter() { if (!fs.existsSync(crashReporterDirectory)) { try { - fs.mkdirSync(crashReporterDirectory); + fs.mkdirSync(crashReporterDirectory, { recursive: true }); } catch (error) { console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); app.exit(1); diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 7d18928f6b4..057aa8748ae 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -18,9 +18,8 @@ "include": [ "typings/require.d.ts", "typings/thenable.d.ts", - "vs/css.d.ts", + "vs/loader.d.ts", "vs/monaco.d.ts", - "vs/nls.d.ts", "vs/editor/*", "vs/base/common/*", "vs/base/browser/*", @@ -31,6 +30,7 @@ "node_modules/*", "vs/platform/files/browser/htmlFileSystemProvider.ts", "vs/platform/files/browser/webFileSystemAccess.ts", + "vs/platform/telemetry/*", "vs/platform/assignment/*" ] } diff --git a/src/tsconfig.vscode-dts.json b/src/tsconfig.vscode-dts.json index 4ae9bc7643a..b8607658396 100644 --- a/src/tsconfig.vscode-dts.json +++ b/src/tsconfig.vscode-dts.json @@ -14,6 +14,7 @@ "types": [], "lib": [ "es5", + "ES2015.Iterable" ], }, "include": [ diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index d5ce3f010a9..9902ab953e7 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -7,7 +7,7 @@ "vs/workbench/api/worker/extHostExtensionService.ts", "vs/base/worker/workerMain", "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts", - "vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts" + "vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts" ], "ban-trustedtypes-createpolicy": [ "vs/base/browser/dom.ts", @@ -15,6 +15,7 @@ "vs/base/browser/defaultWorkerFactory.ts", "vs/base/worker/workerMain.ts", "vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts", + "vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts", "vs/editor/browser/view/domLineBreaksComputer.ts", "vs/editor/browser/view/viewLayer.ts", "vs/editor/browser/widget/diffEditorWidget.ts", diff --git a/src/vs/base/browser/broadcast.ts b/src/vs/base/browser/broadcast.ts new file mode 100644 index 00000000000..d7785f42ff8 --- /dev/null +++ b/src/vs/base/browser/broadcast.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getErrorMessage } from 'vs/base/common/errors'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; + +export class BroadcastDataChannel extends Disposable { + + private broadcastChannel: BroadcastChannel | undefined; + + private readonly _onDidReceiveData = this._register(new Emitter()); + readonly onDidReceiveData = this._onDidReceiveData.event; + + constructor(private readonly channelName: string) { + super(); + + // Use BroadcastChannel + if ('BroadcastChannel' in window) { + try { + this.broadcastChannel = new BroadcastChannel(channelName); + const listener = (event: MessageEvent) => { + this._onDidReceiveData.fire(event.data); + }; + this.broadcastChannel.addEventListener('message', listener); + this._register(toDisposable(() => { + if (this.broadcastChannel) { + this.broadcastChannel.removeEventListener('message', listener); + this.broadcastChannel.close(); + } + })); + } catch (error) { + console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error)); + } + } + + // BroadcastChannel is not supported. Use storage. + if (!this.broadcastChannel) { + this.channelName = `BroadcastDataChannel.${channelName}`; + this.createBroadcastChannel(); + } + } + + private createBroadcastChannel(): void { + const listener = (event: StorageEvent) => { + if (event.key === this.channelName && event.newValue) { + this._onDidReceiveData.fire(JSON.parse(event.newValue)); + } + }; + window.addEventListener('storage', listener); + this._register(toDisposable(() => window.removeEventListener('storage', listener))); + } + + /** + * Sends the data to other BroadcastChannel objects set up for this channel. Data can be structured objects, e.g. nested objects and arrays. + * @param data data to broadcast + */ + postData(data: T): void { + if (this.broadcastChannel) { + this.broadcastChannel.postMessage(data); + } else { + // remove previous changes so that event is triggered even if new changes are same as old changes + window.localStorage.removeItem(this.channelName); + window.localStorage.setItem(this.channelName, JSON.stringify(data)); + } + } +} diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index d41cafcdb77..01db9eebc27 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -71,9 +71,7 @@ class DevicePixelRatioMonitor extends Disposable { } private _handleChange(fireEvent: boolean): void { - if (this._mediaQueryList) { - this._mediaQueryList.removeEventListener('change', this._listener); - } + this._mediaQueryList?.removeEventListener('change', this._listener); this._mediaQueryList = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); this._mediaQueryList.addEventListener('change', this._listener); @@ -196,9 +194,16 @@ export const isAndroid = (userAgent.indexOf('Android') >= 0); let standalone = false; if (window.matchMedia) { - const matchMedia = window.matchMedia('(display-mode: standalone)'); - standalone = matchMedia.matches; - addMatchMediaChangeListener(matchMedia, ({ matches }) => { + const standaloneMatchMedia = window.matchMedia('(display-mode: standalone)'); + const fullScreenMatchMedia = window.matchMedia('(display-mode: fullscreen)'); + standalone = standaloneMatchMedia.matches; + addMatchMediaChangeListener(standaloneMatchMedia, ({ matches }) => { + // entering fullscreen would change standaloneMatchMedia.matches to false + // if standalone is true (running as PWA) and entering fullscreen, skip this change + if (standalone && fullScreenMatchMedia.matches) { + return; + } + // otherwise update standalone (browser to PWA or PWA to browser) standalone = matches; }); } diff --git a/src/vs/base/browser/defaultWorkerFactory.ts b/src/vs/base/browser/defaultWorkerFactory.ts index 93b87e6a563..c57e67f3698 100644 --- a/src/vs/base/browser/defaultWorkerFactory.ts +++ b/src/vs/base/browser/defaultWorkerFactory.ts @@ -86,15 +86,11 @@ class WebWorker implements IWorker { } public postMessage(message: any, transfer: Transferable[]): void { - if (this.worker) { - this.worker.then(w => w.postMessage(message, transfer)); - } + this.worker?.then(w => w.postMessage(message, transfer)); } public dispose(): void { - if (this.worker) { - this.worker.then(w => w.terminate()); - } + this.worker?.then(w => w.terminate()); this.worker = null; } } diff --git a/src/vs/base/browser/deviceAccess.ts b/src/vs/base/browser/deviceAccess.ts new file mode 100644 index 00000000000..17cb1beb028 --- /dev/null +++ b/src/vs/base/browser/deviceAccess.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://wicg.github.io/webusb/ + +export interface UsbDeviceData { + readonly deviceClass: number; + readonly deviceProtocol: number; + readonly deviceSubclass: number; + readonly deviceVersionMajor: number; + readonly deviceVersionMinor: number; + readonly deviceVersionSubminor: number; + readonly manufacturerName?: string; + readonly productId: number; + readonly productName?: string; + readonly serialNumber?: string; + readonly usbVersionMajor: number; + readonly usbVersionMinor: number; + readonly usbVersionSubminor: number; + readonly vendorId: number; +} + +export async function requestUsbDevice(options?: { filters?: unknown[] }): Promise { + const usb = (navigator as any).usb; + if (!usb) { + return undefined; + } + + const device = await usb.requestDevice({ filters: options?.filters ?? [] }); + if (!device) { + return undefined; + } + + return { + deviceClass: device.deviceClass, + deviceProtocol: device.deviceProtocol, + deviceSubclass: device.deviceSubclass, + deviceVersionMajor: device.deviceVersionMajor, + deviceVersionMinor: device.deviceVersionMinor, + deviceVersionSubminor: device.deviceVersionSubminor, + manufacturerName: device.manufacturerName, + productId: device.productId, + productName: device.productName, + serialNumber: device.serialNumber, + usbVersionMajor: device.usbVersionMajor, + usbVersionMinor: device.usbVersionMinor, + usbVersionSubminor: device.usbVersionSubminor, + vendorId: device.vendorId, + }; +} + +// https://wicg.github.io/serial/ + +export interface SerialPortData { + readonly usbVendorId?: number | undefined; + readonly usbProductId?: number | undefined; +} + +export async function requestSerialPort(options?: { filters?: unknown[] }): Promise { + const serial = (navigator as any).serial; + if (!serial) { + return undefined; + } + + const port = await serial.requestPort({ filters: options?.filters ?? [] }); + if (!port) { + return undefined; + } + + const info = port.getInfo(); + return { + usbVendorId: info.usbVendorId, + usbProductId: info.usbProductId + }; +} + +// https://wicg.github.io/webhid/ + +export interface HidDeviceData { + readonly opened: boolean; + readonly vendorId: number; + readonly productId: number; + readonly productName: string; + readonly collections: []; +} + +export async function requestHidDevice(options?: { filters?: unknown[] }): Promise { + const hid = (navigator as any).hid; + if (!hid) { + return undefined; + } + + const devices = await hid.requestDevice({ filters: options?.filters ?? [] }); + if (!devices.length) { + return undefined; + } + + const device = devices[0]; + return { + opened: device.opened, + vendorId: device.vendorId, + productId: device.productId, + productName: device.productName, + collections: device.collections + }; +} diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index e1a0872271a..8b557e01bef 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -92,24 +92,3 @@ export interface IDragAndDropData { update(dataTransfer: DataTransfer): void; getData(): unknown; } - -export class DragAndDropData implements IDragAndDropData { - - constructor(private data: T) { } - - update(): void { - // noop - } - - getData(): T { - return this.data; - } -} - -export interface IStaticDND { - CurrentDragAndDropData: IDragAndDropData | undefined; -} - -export const StaticDND: IStaticDND = { - CurrentDragAndDropData: undefined -}; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d8a7ac484c4..1c69a349c09 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -19,9 +19,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; export function clearNode(node: HTMLElement): void { - while (node.firstChild) { - node.firstChild.remove(); - } + node.replaceChildren(); } /** @@ -31,40 +29,20 @@ export function isInDOM(node: Node | null): boolean { return node?.isConnected ?? false; } -class DomListener implements IDisposable { - - private _handler: (e: any) => void; - private _node: EventTarget; - private readonly _type: string; - private readonly _options: boolean | AddEventListenerOptions; - - constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { - this._node = node; - this._type = type; - this._handler = handler; - this._options = (options || false); - this._node.addEventListener(this._type, this._handler, this._options); - } - - public dispose(): void { - if (!this._handler) { - // Already disposed - return; - } - - this._node.removeEventListener(this._type, this._handler, this._options); - - // Prevent leakers from holding on to the dom or handler func - this._node = null!; - this._handler = null!; - } -} - -export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, options: AddEventListenerOptions): IDisposable; +export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable; export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCaptureOrOptions?: boolean | AddEventListenerOptions): IDisposable { - return new DomListener(node, type, handler, useCaptureOrOptions); + let controller: AbortController | undefined = new AbortController(); + + const opts: AddEventListenerOptions = typeof useCaptureOrOptions === 'boolean' + ? { capture: useCaptureOrOptions, signal: controller.signal } + : { signal: controller.signal, ...(useCaptureOrOptions ?? {}) }; + + node.addEventListener(type, handler, opts); + return toDisposable(() => { + controller?.abort(); + controller = undefined; + }); } export interface IAddStandardDisposableListenerSignature { @@ -122,53 +100,6 @@ export function addDisposableGenericMouseMoveListener(node: EventTarget, handler export function addDisposableGenericMouseUpListener(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable { return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture); } -export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable { - return addDisposableListener(node, 'mouseout', (e: MouseEvent) => { - // Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements - let toElement: Node | null = (e.relatedTarget); - while (toElement && toElement !== node) { - toElement = toElement.parentNode; - } - if (toElement === node) { - return; - } - - handler(e); - }); -} - -export function addDisposableNonBubblingPointerOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable { - return addDisposableListener(node, 'pointerout', (e: MouseEvent) => { - // Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements - let toElement: Node | null = (e.relatedTarget); - while (toElement && toElement !== node) { - toElement = toElement.parentNode; - } - if (toElement === node) { - return; - } - - handler(e); - }); -} - -export function createEventEmitter(target: HTMLElement, type: K, options?: boolean | AddEventListenerOptions): event.Emitter { - let domListener: DomListener | null = null; - const handler = (e: HTMLElementEventMap[K]) => result.fire(e); - const onFirstListenerAdd = () => { - if (!domListener) { - domListener = new DomListener(target, type, handler, options); - } - }; - const onLastListenerRemove = () => { - if (domListener) { - domListener.dispose(); - domListener = null; - } - }; - const result = new event.Emitter({ onFirstListenerAdd, onLastListenerRemove }); - return result; -} interface IRequestAnimationFrame { (callback: (time: number) => void): number; @@ -392,16 +323,8 @@ class SizeUtils { } private static getDimension(element: HTMLElement, cssPropertyName: string, jsPropertyName: string): number { - const computedStyle: CSSStyleDeclaration = getComputedStyle(element); - let value = '0'; - if (computedStyle) { - if (computedStyle.getPropertyValue) { - value = computedStyle.getPropertyValue(cssPropertyName); - } else { - // IE8 - value = (computedStyle).getAttribute(jsPropertyName); - } - } + const computedStyle = getComputedStyle(element); + const value = computedStyle ? computedStyle.getPropertyValue(cssPropertyName) : '0'; return SizeUtils.convertToPixels(element, value); } @@ -570,8 +493,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition { const bb = domNode.getBoundingClientRect(); return { - left: bb.left + StandardWindow.scrollX, - top: bb.top + StandardWindow.scrollY, + left: bb.left + window.scrollX, + top: bb.top + window.scrollY, width: bb.width, height: bb.height }; @@ -595,30 +518,6 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { return zoom; } -export interface IStandardWindow { - readonly scrollX: number; - readonly scrollY: number; -} - -export const StandardWindow: IStandardWindow = new class implements IStandardWindow { - get scrollX(): number { - if (typeof window.scrollX === 'number') { - // modern browsers - return window.scrollX; - } else { - return document.body.scrollLeft + document.documentElement!.scrollLeft; - } - } - - get scrollY(): number { - if (typeof window.scrollY === 'number') { - // modern browsers - return window.scrollY; - } else { - return document.body.scrollTop + document.documentElement!.scrollTop; - } - } -}; // Adapted from WinJS // Gets the width of the element, including margins. @@ -870,6 +769,7 @@ export const EventType = { POINTER_UP: 'pointerup', POINTER_DOWN: 'pointerdown', POINTER_MOVE: 'pointermove', + POINTER_LEAVE: 'pointerleave', CONTEXT_MENU: 'contextmenu', WHEEL: 'wheel', // Keyboard @@ -920,22 +820,12 @@ export interface EventLike { } export const EventHelper = { - stop: function (e: EventLike, cancelBubble?: boolean) { - if (e.preventDefault) { - e.preventDefault(); - } else { - // IE8 - (e).returnValue = false; - } - + stop: (e: T, cancelBubble?: boolean): T => { + e.preventDefault(); if (cancelBubble) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - // IE8 - (e).cancelBubble = true; - } + e.stopPropagation(); } + return e; } }; @@ -1177,18 +1067,12 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void { // in the hierarchy of the parent DOM nodes. if (document.activeElement === node) { const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex'); - if (parentFocusable) { - parentFocusable.focus(); - } + parentFocusable?.focus(); } node.removeAttribute('tabindex'); } -export function getElementsByTagName(tag: string): HTMLElement[] { - return Array.prototype.slice.call(document.getElementsByTagName(tag), 0); -} - export function finalHandler(fn: (event: T) => any): (event: T) => any { return e => { e.preventDefault(); @@ -1685,18 +1569,6 @@ export function getCookieValue(name: string): string | undefined { return match ? match.pop() : undefined; } -export const enum ZIndex { - SASH = 35, - SuggestWidget = 40, - Hover = 50, - DragImage = 1000, - MenubarMenuItemsHolder = 2000, // quick-input-widget - ContextView = 2500, - ModalDialog = 2600, - PaneDropOverlay = 10000 -} - - export interface IDragAndDropObserverCallbacks { readonly onDragEnter: (e: DragEvent) => void; readonly onDragLeave: (e: DragEvent) => void; @@ -1773,3 +1645,154 @@ export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly return { top, right, bottom, left }; } + +type HTMLElementAttributeKeys = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys : T[K] }>; +type ElementAttributes = HTMLElementAttributeKeys & Record; +type RemoveHTMLElement = T extends HTMLElement ? never : T; +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; +type ArrayToObj = UnionToIntersection>; +type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement }; + +type TagToElement = T extends `${infer TStart}#${string}` + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] + : HTMLElement + : T extends `${infer TStart}.${string}` + ? TStart extends keyof HHTMLElementTagNameMap + ? HHTMLElementTagNameMap[TStart] + : HTMLElement + : T extends keyof HTMLElementTagNameMap + ? HTMLElementTagNameMap[T] + : HTMLElement; + +type TagToElementAndId = TTag extends `${infer TTag}@${infer TId}` + ? { element: TagToElement; id: TId } + : { element: TagToElement; id: 'root' }; + +type TagToRecord = TagToElementAndId extends { element: infer TElement; id: infer TId } + ? Record<(TId extends string ? TId : never) | 'root', TElement> + : never; + +type Child = HTMLElement | string | Record; +type Children = [] + | [Child] + | [Child, Child] + | [Child, Child, Child] + | [Child, Child, Child, Child] + | [Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] + | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]; + +const H_REGEX = /(?[\w\-]+)?(?:#(?[\w\-]+))?(?(?:\.(?:[\w\-]+))*)(?:@(?(?:[\w\_])+))?/; + +/** + * A helper function to create nested dom nodes. + * + * + * ```ts + * const elements = h('div.code-view', [ + * h('div.title@title'), + * h('div.container', [ + * h('div.gutter@gutterDiv'), + * h('div@editor'), + * ]), + * ]); + * const editor = createEditor(elements.editor); + * ``` +*/ +export function h + (tag: TTag): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, children: T): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, attributes: Partial>>): + TagToRecord extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h + (tag: TTag, attributes: Partial>>, children: T): + (ArrayToObj & TagToRecord) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; + +export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial> | Record, children?: any[]] | [children: any[]]): Record { + let attributes: { $?: string } & Partial>; + let children: (Record | HTMLElement)[] | undefined; + + if (Array.isArray(args[0])) { + attributes = {}; + children = args[0]; + } else { + attributes = args[0] as any || {}; + children = args[1]; + } + + const match = H_REGEX.exec(tag); + + if (!match || !match.groups) { + throw new Error('Bad use of h'); + } + + const tagName = match.groups['tag'] || 'div'; + const el = document.createElement(tagName); + + if (match.groups['id']) { + el.id = match.groups['id']; + } + + if (match.groups['class']) { + el.className = match.groups['class'].replace(/\./g, ' ').trim(); + } + + const result: Record = {}; + + if (match.groups['name']) { + result[match.groups['name']] = el; + } + + if (children) { + for (const c of children) { + if (c instanceof HTMLElement) { + el.appendChild(c); + } else if (typeof c === 'string') { + el.append(c); + } else { + Object.assign(result, c); + el.appendChild(c.root); + } + } + } + + for (const [key, value] of Object.entries(attributes)) { + if (key === 'style') { + for (const [cssKey, cssValue] of Object.entries(value)) { + el.style.setProperty( + camelCaseToHyphenCase(cssKey), + typeof cssValue === 'number' ? cssValue + 'px' : '' + cssValue + ); + } + } else if (key === 'tabIndex') { + el.tabIndex = value; + } else { + el.setAttribute(camelCaseToHyphenCase(key), value.toString()); + } + } + + result['root'] = el; + + return result; +} + +function camelCaseToHyphenCase(str: string) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index adef057e119..600a5f78248 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -45,18 +45,3 @@ export class DomEmitter implements IDisposable { this.emitter.dispose(); } } - -export interface CancellableEvent { - preventDefault(): void; - stopPropagation(): void; -} - -export function stopEvent(event: T): T { - event.preventDefault(); - event.stopPropagation(); - return event; -} - -export function stop(event: BaseEvent): BaseEvent { - return BaseEvent.map(event, stopEvent); -} diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index daef833851a..2ae545682cd 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -8,7 +8,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { DisposableStore } from 'vs/base/common/lifecycle'; export interface IContentActionHandler { - callback: (content: string, event?: IMouseEvent) => void; + callback: (content: string, event: IMouseEvent) => void; readonly disposables: DisposableStore; } diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts index 5fba9c0ab4c..5d985a1e571 100644 --- a/src/vs/base/browser/indexedDB.ts +++ b/src/vs/base/browser/indexedDB.ts @@ -6,7 +6,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { getErrorMessage } from 'vs/base/common/errors'; import { mark } from 'vs/base/common/performance'; -import { isArray } from 'vs/base/common/types'; class MissingStoresError extends Error { constructor(readonly db: IDBDatabase) { @@ -28,7 +27,7 @@ export class IndexedDB { return new IndexedDB(database, name); } - static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise { + private static async openDatabase(name: string, version: number | undefined, stores: string[]): Promise { mark(`code/willOpenDatabase/${name}`); try { return await IndexedDB.doOpenDatabase(name, version, stores); @@ -106,9 +105,7 @@ export class IndexedDB { if (this.pendingTransactions.length) { this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort()); } - if (this.database) { - this.database.close(); - } + this.database?.close(); this.database = null; } @@ -122,7 +119,7 @@ export class IndexedDB { this.pendingTransactions.push(transaction); return new Promise((c, e) => { transaction.oncomplete = () => { - if (isArray(request)) { + if (Array.isArray(request)) { c(request.map(r => r.result)); } else { c(request.result); diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index a34f4924ba2..5ab51ec37c7 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -9,8 +9,6 @@ import { DomEmitter } from 'vs/base/browser/event'; import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { raceCancellation } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent'; @@ -44,8 +42,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const disposables = new DisposableStore(); let isDisposed = false; - const cts = disposables.add(new CancellationTokenSource()); - const element = createElement(options); const _uriMassage = function (part: string): string { @@ -96,11 +92,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return uri.toString(); }; - // signal to code-block render that the - // element has been created - let signalInnerHTML: () => void; - const withInnerHTML = new Promise(c => signalInnerHTML = c); - const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { @@ -146,24 +137,14 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return `

${text}

`; }; + // Will collect [id, renderedElement] tuples + const codeBlocks: Promise<[string, HTMLElement]>[] = []; + if (options.codeBlockRenderer) { renderer.code = (code, lang) => { - const value = options.codeBlockRenderer!(lang ?? '', code); - // when code-block rendering is async we return sync - // but update the node with the real result later. const id = defaultGenerator.nextId(); - raceCancellation(Promise.all([value, withInnerHTML]), cts.token).then(values => { - if (!isDisposed && values) { - const span = element.querySelector(`div[data-code="${id}"]`); - if (span) { - DOM.reset(span, values[0]); - } - options.asyncRenderCallback?.(); - } - }).catch(() => { - // ignore - }); - + const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), code); + codeBlocks.push(value.then(element => [id, element])); return `
${escape(code)}
`; }; } @@ -265,7 +246,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href) ) { // drop the link - a.replaceWith(a.textContent ?? ''); + a.replaceWith(...a.childNodes); } else { let resolvedHref = _href(href, false); if (markdown.baseUri) { @@ -277,8 +258,22 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string; - // signal that async code blocks can be now be inserted - signalInnerHTML!(); + if (codeBlocks.length > 0) { + Promise.all(codeBlocks).then((tuples) => { + if (isDisposed) { + return; + } + const renderedElements = new Map(tuples); + const placeholderElements = element.querySelectorAll(`div[data-code]`); + for (const placeholderElement of placeholderElements) { + const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); + if (renderedElement) { + DOM.reset(placeholderElement, renderedElement); + } + } + options.asyncRenderCallback?.(); + }); + } // signal size changes for image tags if (options.asyncRenderCallback) { @@ -294,12 +289,23 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende element, dispose: () => { isDisposed = true; - cts.cancel(); disposables.dispose(); } }; } +function postProcessCodeBlockLanguageId(lang: string | undefined): string { + if (!lang) { + return ''; + } + + const parts = lang.split(/[\s+|:|,|\{|\?]/, 1); + if (parts.length) { + return parts[0]; + } + return lang; +} + function resolveWithBaseUri(baseUri: URI, href: string): string { const hasScheme = /^\w[\w\d+.-]*:/.test(href); if (hasScheme) { diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index a4f106a6c19..e6f5d3b5a4b 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -88,28 +88,14 @@ export class StandardMouseEvent implements IMouseEvent { } } -export interface IDataTransfer { - dropEffect: string; - effectAllowed: string; - types: any[]; - files: any[]; - - setData(type: string, data: string): void; - setDragImage(image: any, x: number, y: number): void; - - getData(type: string): string; - clearData(types?: string[]): void; -} - export class DragMouseEvent extends StandardMouseEvent { - public readonly dataTransfer: IDataTransfer; + public readonly dataTransfer: DataTransfer; constructor(e: MouseEvent) { super(e); this.dataTransfer = (e).dataTransfer; } - } export interface IMouseWheelEvent extends MouseEvent { @@ -211,14 +197,10 @@ export class StandardWheelEvent { } public preventDefault(): void { - if (this.browserEvent) { - this.browserEvent.preventDefault(); - } + this.browserEvent?.preventDefault(); } public stopPropagation(): void { - if (this.browserEvent) { - this.browserEvent.stopPropagation(); - } + this.browserEvent?.stopPropagation(); } } diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 61340ea1ba2..0e97d180ef1 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -23,6 +23,7 @@ export interface IBaseActionViewItemOptions { draggable?: boolean; isMenu?: boolean; useEventAsContext?: boolean; + hoverDelegate?: IHoverDelegate; } export class BaseActionViewItem extends Disposable implements IActionViewItem { @@ -32,6 +33,8 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { _context: unknown; readonly _action: IAction; + private customHover?: ICustomHover; + get action() { return this._action; } @@ -92,10 +95,6 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this._actionRunner = actionRunner; } - getAction(): IAction { - return this._action; - } - isEnabled(): boolean { return this._action.enabled; } @@ -210,8 +209,34 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { // implement in subclass } + protected getTooltip(): string | undefined { + return this.action.tooltip; + } + protected updateTooltip(): void { - // implement in subclass + if (!this.element) { + return; + } + const title = this.getTooltip() ?? ''; + this.updateAriaLabel(); + if (!this.options.hoverDelegate) { + this.element.title = title; + } else { + this.element.title = ''; + if (!this.customHover) { + this.customHover = setupCustomHover(this.options.hoverDelegate, this.element, title); + this._store.add(this.customHover); + } else { + this.customHover.update(title); + } + } + } + + protected updateAriaLabel(): void { + if (this.element) { + const title = this.getTooltip() ?? ''; + this.element.setAttribute('aria-label', title); + } } protected updateClass(): void { @@ -236,7 +261,6 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; label?: boolean; keybinding?: string | null; - hoverDelegate?: IHoverDelegate; } export class ActionViewItem extends BaseActionViewItem { @@ -245,7 +269,6 @@ export class ActionViewItem extends BaseActionViewItem { protected override options: IActionViewItemOptions; private cssClass?: string; - private customHover?: ICustomHover; constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { super(context, action, options); @@ -313,41 +336,24 @@ export class ActionViewItem extends BaseActionViewItem { override updateLabel(): void { if (this.options.label && this.label) { - this.label.textContent = this.getAction().label; + this.label.textContent = this.action.label; } } - override updateTooltip(): void { + override getTooltip() { let title: string | null = null; - if (this.getAction().tooltip) { - title = this.getAction().tooltip; + if (this.action.tooltip) { + title = this.action.tooltip; - } else if (!this.options.label && this.getAction().label && this.options.icon) { - title = this.getAction().label; + } else if (!this.options.label && this.action.label && this.options.icon) { + title = this.action.label; if (this.options.keybinding) { title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding); } } - this._applyUpdateTooltip(title); - } - - protected _applyUpdateTooltip(title: string | undefined | null): void { - if (title && this.label) { - this.label.setAttribute('aria-label', title); - if (!this.options.hoverDelegate) { - this.label.title = title; - } else { - this.label.title = ''; - if (!this.customHover) { - this.customHover = setupCustomHover(this.options.hoverDelegate, this.label, title); - this._store.add(this.customHover); - } else { - this.customHover.update(title); - } - } - } + return title ?? undefined; } override updateClass(): void { @@ -356,7 +362,7 @@ export class ActionViewItem extends BaseActionViewItem { } if (this.options.icon) { - this.cssClass = this.getAction().class; + this.cssClass = this.action.class; if (this.label) { this.label.classList.add('codicon'); @@ -367,37 +373,38 @@ export class ActionViewItem extends BaseActionViewItem { this.updateEnabled(); } else { - if (this.label) { - this.label.classList.remove('codicon'); - } + this.label?.classList.remove('codicon'); } } override updateEnabled(): void { - if (this.getAction().enabled) { + if (this.action.enabled) { if (this.label) { this.label.removeAttribute('aria-disabled'); this.label.classList.remove('disabled'); } - if (this.element) { - this.element.classList.remove('disabled'); - } + this.element?.classList.remove('disabled'); } else { if (this.label) { this.label.setAttribute('aria-disabled', 'true'); this.label.classList.add('disabled'); } - if (this.element) { - this.element.classList.add('disabled'); - } + this.element?.classList.add('disabled'); + } + } + + override updateAriaLabel(): void { + if (this.label) { + const title = this.getTooltip() ?? ''; + this.label.setAttribute('aria-label', title); } } override updateChecked(): void { if (this.label) { - if (this.getAction().checked) { + if (this.action.checked) { this.label.classList.add('checked'); } else { this.label.classList.remove('checked'); @@ -442,15 +449,11 @@ export class SelectActionViewItem extends BaseActionViewItem { } override focus(): void { - if (this.selectBox) { - this.selectBox.focus(); - } + this.selectBox?.focus(); } override blur(): void { - if (this.selectBox) { - this.selectBox.blur(); - } + this.selectBox?.blur(); } override render(container: HTMLElement): void { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 4d210d5c16a..45e5f3800a3 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -46,6 +46,7 @@ } .monaco-action-bar .action-label { + display: flex; font-size: 11px; padding: 3px; border-radius: 5px; @@ -105,6 +106,12 @@ display: flex; } -.monaco-action-bar .action-item.action-dropdown-item > .action-label { - margin-right: 1px; +.monaco-action-bar .action-item.action-dropdown-item > .action-dropdown-item-separator { + display: flex; + align-items: center; + cursor: default; +} + +.monaco-action-bar .action-item.action-dropdown-item > .action-dropdown-item-separator > div { + width: 1px; } diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index ed4131eefd3..36beeb00478 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -8,6 +8,7 @@ display: flex; width: 100%; padding: 4px; + border-radius: 2px; text-align: center; cursor: pointer; justify-content: center; @@ -38,8 +39,36 @@ cursor: pointer; } -.monaco-button-dropdown > .monaco-dropdown-button { - margin-left: 1px; +.monaco-button-dropdown.disabled { + cursor: default; +} + +.monaco-button-dropdown > .monaco-button:focus { + outline-offset: -1px !important; +} + +.monaco-button-dropdown.disabled > .monaco-button.disabled, +.monaco-button-dropdown.disabled > .monaco-button.disabled:focus, +.monaco-button-dropdown.disabled > .monaco-button-dropdown-separator { + opacity: 0.4 !important; +} + +.monaco-button-dropdown > .monaco-button.monaco-text-button { + border-right-width: 0 !important; +} + +.monaco-button-dropdown .monaco-button-dropdown-separator { + padding: 4px 0; + cursor: default; +} + +.monaco-button-dropdown .monaco-button-dropdown-separator > div { + height: 100%; + width: 1px; +} + +.monaco-button-dropdown > .monaco-button.monaco-dropdown-button { + border-left-width: 0 !important; } .monaco-description-button { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 721059b4a27..855ba9e0c90 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -15,6 +15,7 @@ import { Emitter, Event as BaseEvent } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; +import { localize } from 'vs/nls'; import 'vs/css!./button'; export interface IButtonOptions extends IButtonStyles { @@ -27,6 +28,7 @@ export interface IButtonStyles { buttonBackground?: Color; buttonHoverBackground?: Color; buttonForeground?: Color; + buttonSeparator?: Color; buttonSecondaryBackground?: Color; buttonSecondaryHoverBackground?: Color; buttonSecondaryForeground?: Color; @@ -36,6 +38,7 @@ export interface IButtonStyles { const defaultOptions: IButtonStyles = { buttonBackground: Color.fromHex('#0E639C'), buttonHoverBackground: Color.fromHex('#006BB3'), + buttonSeparator: Color.white, buttonForeground: Color.white }; @@ -136,8 +139,8 @@ export class Button extends Disposable implements IButton { // Also set hover background when button is focused for feedback this.focusTracker = this._register(trackFocus(this._element)); - this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground())); - this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles + this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } })); + this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } })); this.applyStyles(); } @@ -238,6 +241,7 @@ export interface IButtonWithDropdownOptions extends IButtonOptions { readonly contextMenuProvider: IContextMenuProvider; readonly actions: IAction[]; readonly actionRunner?: IActionRunner; + readonly addPrimaryActionToDropdown?: boolean; } export class ButtonWithDropdown extends Disposable implements IButton { @@ -245,6 +249,8 @@ export class ButtonWithDropdown extends Disposable implements IButton { private readonly button: Button; private readonly action: Action; private readonly dropdownButton: Button; + private readonly separatorContainer: HTMLDivElement; + private readonly separator: HTMLDivElement; readonly element: HTMLElement; private readonly _onDidClick = this._register(new Emitter()); @@ -261,13 +267,23 @@ export class ButtonWithDropdown extends Disposable implements IButton { this._register(this.button.onDidClick(e => this._onDidClick.fire(e))); this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined))); + this.separatorContainer = document.createElement('div'); + this.separatorContainer.classList.add('monaco-button-dropdown-separator'); + + this.separator = document.createElement('div'); + this.separatorContainer.appendChild(this.separator); + this.element.appendChild(this.separatorContainer); + this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); + this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); + this.dropdownButton.element.setAttribute('aria-haspopup', 'true'); + this.dropdownButton.element.setAttribute('aria-expanded', 'false'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); this.dropdownButton.icon = Codicon.dropDownButton; this._register(this.dropdownButton.onDidClick(e => { options.contextMenuProvider.showContextMenu({ getAnchor: () => this.dropdownButton.element, - getActions: () => [this.action, ...options.actions], + getActions: () => options.addPrimaryActionToDropdown === false ? [...options.actions] : [this.action, ...options.actions], actionRunner: options.actionRunner, onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false') }); @@ -287,6 +303,8 @@ export class ButtonWithDropdown extends Disposable implements IButton { set enabled(enabled: boolean) { this.button.enabled = enabled; this.dropdownButton.enabled = enabled; + + this.element.classList.toggle('disabled', !enabled); } get enabled(): boolean { @@ -296,6 +314,20 @@ export class ButtonWithDropdown extends Disposable implements IButton { style(styles: IButtonStyles): void { this.button.style(styles); this.dropdownButton.style(styles); + + // Separator + const border = styles.buttonBorder ? styles.buttonBorder.toString() : ''; + + this.separatorContainer.style.borderTopWidth = border ? '1px' : ''; + this.separatorContainer.style.borderTopStyle = border ? 'solid' : ''; + this.separatorContainer.style.borderTopColor = border; + + this.separatorContainer.style.borderBottomWidth = border ? '1px' : ''; + this.separatorContainer.style.borderBottomStyle = border ? 'solid' : ''; + this.separatorContainer.style.borderBottomColor = border; + + this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? ''; + this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? ''; } focus(): void { @@ -319,12 +351,10 @@ export class ButtonWithDescription extends Button implements IButtonWithDescript this._labelElement = document.createElement('div'); this._labelElement.classList.add('monaco-button-label'); - this._labelElement.tabIndex = -1; this._element.appendChild(this._labelElement); this._descriptionElement = document.createElement('div'); this._descriptionElement.classList.add('monaco-button-description'); - this._descriptionElement.tabIndex = -1; this._element.appendChild(this._descriptionElement); } diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index b16ac4334ca..b746382e57a 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -159,9 +159,7 @@ export class CenteredViewLayout implements IDisposable { this.container.removeChild(this.splitView.el); } this.splitViewDisposables.clear(); - if (this.splitView) { - this.splitView.dispose(); - } + this.splitView?.dispose(); this.splitView = undefined; this.emptyViews = undefined; this.container.appendChild(this.view.element); diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 94cbe346ed7..d8ac87cba86 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/contextview/contextview.css b/src/vs/base/browser/ui/contextview/contextview.css index bb7ebbcfdb2..cca41507ae9 100644 --- a/src/vs/base/browser/ui/contextview/contextview.css +++ b/src/vs/base/browser/ui/contextview/contextview.css @@ -5,7 +5,6 @@ .context-view { position: absolute; - z-index: 2500; } .context-view.fixed { @@ -13,6 +12,5 @@ font-family: inherit; font-size: 13px; position: fixed; - z-index: 2500; color: inherit; } diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index c75402d6aea..27958b235af 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -206,7 +206,7 @@ export class ContextView extends Disposable { this.view.className = 'context-view'; this.view.style.top = '0px'; this.view.style.left = '0px'; - this.view.style.zIndex = '2500'; + this.view.style.zIndex = '2575'; this.view.style.position = this.useFixedPosition ? 'fixed' : 'absolute'; DOM.show(this.view); diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 0643cf674a6..b8f106ef7af 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -427,13 +427,9 @@ export class Dialog extends Disposable { this.element.style.backgroundColor = bgColor?.toString() ?? ''; this.element.style.border = border; - if (this.buttonBar) { - this.buttonBar.buttons.forEach(button => button.style(style)); - } + this.buttonBar?.buttons.forEach(button => button.style(style)); - if (this.checkbox) { - this.checkbox.style(style); - } + this.checkbox?.style(style); if (fgColor && bgColor) { const messageDetailColor = fgColor.transparent(.9); diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index b45468dd137..b0f07682dd6 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -56,8 +56,10 @@ export class BaseDropdown extends ActionRunner { for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) { this._register(addDisposableListener(this._label, event, e => { - if (e instanceof MouseEvent && e.detail > 1) { - return; // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363) + if (e instanceof MouseEvent && (e.detail > 1 || e.button !== 0)) { + // prevent right click trigger to allow separate context menu (https://github.com/microsoft/vscode/issues/151064) + // prevent multiple clicks to open multiple context menus (https://github.com/microsoft/vscode/issues/41363) + return; } if (this.visible) { @@ -190,9 +192,7 @@ export class Dropdown extends BaseDropdown { override hide(): void { super.hide(); - if (this.contextViewProvider) { - this.contextViewProvider.hideContextView(); - } + this.contextViewProvider?.hideContextView(); } protected renderContents(container: HTMLElement): IDisposable | null { diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 0af76e3b6a0..75d35b2b4d7 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -import { $, addDisposableListener, append, EventType } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, EventType, h } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; @@ -126,9 +127,22 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { }; } + this.updateTooltip(); this.updateEnabled(); } + override getTooltip(): string | undefined { + let title: string | null = null; + + if (this.action.tooltip) { + title = this.action.tooltip; + } else if (this.action.label) { + title = this.action.label; + } + + return title ?? undefined; + } + override setActionContext(newContext: unknown): void { super.setActionContext(newContext); @@ -142,13 +156,11 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } show(): void { - if (this.dropdownMenu) { - this.dropdownMenu.show(); - } + this.dropdownMenu?.show(); } protected override updateEnabled(): void { - const disabled = !this.getAction().enabled; + const disabled = !this.action.enabled; this.actionItem?.classList.toggle('disabled', disabled); this.element?.classList.toggle('disabled', disabled); } @@ -182,7 +194,13 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { return Array.isArray(actionsProvider) ? actionsProvider : (actionsProvider as IActionProvider).getActions(); // TODO: microsoft/TypeScript#42768 } }; - this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(this.options).menuActionClassNames || []] }); + + const menuActionClassNames = (this.options).menuActionClassNames || []; + const separator = h('div.action-dropdown-item-separator', [h('div', {})]).root; + separator.classList.toggle('prominent', menuActionClassNames.includes('prominent')); + append(this.element, separator); + + this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...menuActionClassNames] }); this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 4c40cba159f..b637cf45667 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles'; import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles { readonly appendWholeWordsLabel?: string; readonly appendRegexLabel?: string; readonly history?: string[]; + readonly additionalToggles?: Toggle[]; readonly showHistoryHint?: () => boolean; } @@ -74,6 +75,7 @@ export class FindInput extends Widget { protected regex: RegexToggle; protected wholeWords: WholeWordsToggle; protected caseSensitive: CaseSensitiveToggle; + protected additionalToggles: Toggle[] = []; public domNode: HTMLElement; public inputBox: HistoryInputBox; @@ -209,10 +211,6 @@ export class FindInput extends Widget { this._onCaseSensitiveKeyDown.fire(e); })); - if (this._showOptionButtons) { - this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width(); - } - // Arrow-Key support to navigate between options const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; this.onkeydown(this.domNode, (event: IKeyboardEvent) => { @@ -250,11 +248,37 @@ export class FindInput extends Widget { this.controls.appendChild(this.wholeWords.domNode); this.controls.appendChild(this.regex.domNode); + if (!this._showOptionButtons) { + this.caseSensitive.domNode.style.display = 'none'; + this.wholeWords.domNode.style.display = 'none'; + this.regex.domNode.style.display = 'none'; + } + + for (const toggle of options?.additionalToggles ?? []) { + this._register(toggle); + this.controls.appendChild(toggle.domNode); + + this._register(toggle.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + })); + + this.additionalToggles.push(toggle); + } + + if (this.additionalToggles.length > 0) { + this.controls.style.display = 'block'; + } + + this.inputBox.paddingRight = + (this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0) + + this.additionalToggles.reduce((r, t) => r + t.width(), 0); + this.domNode.appendChild(this.controls); - if (parent) { - parent.appendChild(this.domNode); - } + parent?.appendChild(this.domNode); this._register(dom.addDisposableListener(this.inputBox.inputElement, 'compositionstart', (e: CompositionEvent) => { this.imeSessionInProgress = true; @@ -284,6 +308,10 @@ export class FindInput extends Widget { this.regex.enable(); this.wholeWords.enable(); this.caseSensitive.enable(); + + for (const toggle of this.additionalToggles) { + toggle.enable(); + } } public disable(): void { @@ -292,6 +320,10 @@ export class FindInput extends Widget { this.regex.disable(); this.wholeWords.disable(); this.caseSensitive.disable(); + + for (const toggle of this.additionalToggles) { + toggle.disable(); + } } public setFocusInputOnOptionClick(value: boolean): void { @@ -358,6 +390,10 @@ export class FindInput extends Widget { this.wholeWords.style(toggleStyles); this.caseSensitive.style(toggleStyles); + for (const toggle of this.additionalToggles) { + toggle.style(toggleStyles); + } + const inputBoxStyles: IInputBoxStyles = { inputBackground: this.inputBackground, inputForeground: this.inputForeground, diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 46b4014bb3b..2861edde0b8 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -225,9 +225,7 @@ export class ReplaceInput extends Widget { this.domNode.appendChild(controls); - if (parent) { - parent.appendChild(this.domNode); - } + parent?.appendChild(this.domNode); this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e)); this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e)); @@ -355,27 +353,19 @@ export class ReplaceInput extends Widget { } public validate(): void { - if (this.inputBox) { - this.inputBox.validate(); - } + this.inputBox?.validate(); } public showMessage(message: InputBoxMessage): void { - if (this.inputBox) { - this.inputBox.showMessage(message); - } + this.inputBox?.showMessage(message); } public clearMessage(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } + this.inputBox?.hideMessage(); } private clearValidation(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } + this.inputBox?.hideMessage(); } public set width(newWidth: number) { diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index e4cbed18667..cf8798982b0 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -527,6 +527,16 @@ export class Grid extends Disposable { return this.gridview.resizeView(location, size); } + /** + * Returns whether all other {@link IView views} are at their minimum size. + * + * @param view The reference {@link IView view}. + */ + isViewSizeMaximized(view: T): boolean { + const location = this.getViewLocation(view); + return this.gridview.isViewSizeMaximized(location); + } + /** * Get the size of a {@link IView view}. * diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index e121bcc10c8..8457663d657 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -592,6 +592,10 @@ class BranchNode implements ISplitView, IDisposable { this.splitview.resizeView(index, size); } + isChildSizeMaximized(index: number): boolean { + return this.splitview.isViewSizeMaximized(index); + } + distributeViewSizes(recursive = false): void { this.splitview.distributeViewSizes(); @@ -1431,6 +1435,27 @@ export class GridView implements IDisposable { } } + /** + * Returns whether all other {@link IView views} are at their minimum size. + * + * @param location The {@link GridLocation location} of the view. + */ + isViewSizeMaximized(location: GridLocation): boolean { + const [ancestors, node] = this.getNode(location); + + if (!(node instanceof LeafNode)) { + throw new Error('Invalid location'); + } + + for (let i = 0; i < ancestors.length; i++) { + if (!ancestors[i].isChildSizeMaximized(location[i])) { + return false; + } + } + + return true; + } + /** * Distribute the size among all {@link IView views} within the entire * grid or within a single {@link SplitView}. diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 3b69763252e..18c2d093f00 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -96,10 +96,10 @@ export class HighlightedLabel { if (pos < highlight.start) { const substring = this.text.substring(pos, highlight.start); children.push(dom.$('span', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring])); - pos = highlight.end; + pos = highlight.start; } - const substring = this.text.substring(highlight.start, highlight.end); + const substring = this.text.substring(pos, highlight.end); const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]); if (highlight.extraClasses) { diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 79f13f8dad1..1dd1e111bd9 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -12,7 +13,7 @@ export interface IHoverDelegateTarget extends IDisposable { x?: number; } -export interface IHoverDelegateOptions { +export interface IHoverDelegateOptions extends IUpdatableHoverOptions { content: IMarkdownString | string | HTMLElement; target: IHoverDelegateTarget | HTMLElement; hoverPosition?: HoverPosition; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 5e7ff8fa7de..dbd32cc5e67 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -30,6 +30,7 @@ export interface IIconLabelValueOptions { matches?: IMatch[]; labelEscapeNewLines?: boolean; descriptionMatches?: IMatch[]; + disabledCommand?: boolean; readonly separator?: string; readonly domId?: string; } @@ -124,22 +125,28 @@ export class IconLabel extends Disposable { } setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void { - const classes = ['monaco-icon-label']; + const labelClasses = ['monaco-icon-label']; + const containerClasses = ['monaco-icon-label-container']; if (options) { if (options.extraClasses) { - classes.push(...options.extraClasses); + labelClasses.push(...options.extraClasses); } if (options.italic) { - classes.push('italic'); + labelClasses.push('italic'); } if (options.strikethrough) { - classes.push('strikethrough'); + labelClasses.push('strikethrough'); + } + + if (options.disabledCommand) { + containerClasses.push('disabled'); } } - this.domNode.className = classes.join(' '); + this.domNode.className = labelClasses.join(' '); + this.labelContainer.className = containerClasses.join(' '); this.setupHover(options?.descriptionTitle ? this.labelContainer : this.element, options?.title); this.nameNode.setLabel(label, options); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 3034ea52d93..41e4c168286 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -33,6 +33,21 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo export type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined; +/** + * Copied from src\vs\workbench\services\hover\browser\hover.ts + * @deprecated Use IHoverService + */ +export interface IHoverAction { + label: string; + commandId: string; + iconClass?: string; + run(target: HTMLElement): void; +} + +export interface IUpdatableHoverOptions { + actions?: IHoverAction[]; + linkHandler?(url: string): void; +} export interface ICustomHover extends IDisposable { @@ -49,7 +64,7 @@ export interface ICustomHover extends IDisposable { /** * Updates the contents of the hover. */ - update(tooltip: IHoverContent): void; + update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void; } @@ -61,7 +76,7 @@ class UpdatableHoverWidget implements IDisposable { constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) { } - async update(content: IHoverContent, focus?: boolean): Promise { + async update(content: IHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): Promise { if (this._cancellationTokenSource) { // there's an computation ongoing, cancel it this._cancellationTokenSource.dispose(true); @@ -99,10 +114,10 @@ class UpdatableHoverWidget implements IDisposable { } } - this.show(resolvedContent, focus); + this.show(resolvedContent, focus, options); } - private show(content: IResolvedHoverContent, focus?: boolean): void { + private show(content: IResolvedHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): void { const oldHoverWidget = this._hoverWidget; if (this.hasContent(content)) { @@ -111,7 +126,8 @@ class UpdatableHoverWidget implements IDisposable { target: this.target, showPointer: this.hoverDelegate.placement === 'element', hoverPosition: HoverPosition.BELOW, - skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget // do not fade in if the hover is already showing + skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing + ...options }; this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus); @@ -142,7 +158,7 @@ class UpdatableHoverWidget implements IDisposable { } } -export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent): ICustomHover { +export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover { let hoverPreparation: IDisposable | undefined; let hoverWidget: UpdatableHoverWidget | undefined; @@ -163,7 +179,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM return new TimeoutTimer(async () => { if (!hoverWidget || hoverWidget.isDisposed) { hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0); - await hoverWidget.update(content, focus); + await hoverWidget.update(content, focus, options); } }, delay); }; @@ -208,9 +224,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hide: () => { hideHover(true, true); }, - update: async newContent => { + update: async (newContent, hoverOptions) => { content = newContent; - await hoverWidget?.update(content); + await hoverWidget?.update(content, undefined, hoverOptions); }, dispose: () => { mouseOverDomEmitter.dispose(); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 45b73bece04..ab4c0e131eb 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -31,6 +31,9 @@ flex-shrink: 0; /* fix for https://github.com/microsoft/vscode/issues/13787 */ } +.monaco-icon-label-container.disabled { + color: var(--vscode-disabledForeground); +} .monaco-icon-label > .monaco-icon-label-container { min-width: 0; overflow: hidden; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index a3087e1c9c1..ae89cb33f38 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -621,9 +621,7 @@ export class InputBox extends Widget { this.message = null; - if (this.actionbar) { - this.actionbar.dispose(); - } + this.actionbar?.dispose(); super.dispose(); } diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index eaca3ae8bd8..84cda4cfae3 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -65,72 +65,7 @@ z-index: 1000; } -/* Type filter */ - -.monaco-list-type-filter { - display: flex; - align-items: center; - position: absolute; - border-radius: 2px; - padding: 0px 3px; - max-width: calc(100% - 10px); - text-overflow: ellipsis; - overflow: hidden; - text-align: right; - box-sizing: border-box; - cursor: all-scroll; - font-size: 13px; - line-height: 18px; - height: 20px; - z-index: 1; - top: 4px; -} - -.monaco-list-type-filter.dragging { - transition: top 0.2s, left 0.2s; -} - -.monaco-list-type-filter.ne { - right: 4px; -} - -.monaco-list-type-filter.nw { - left: 4px; -} - -.monaco-list-type-filter > .controls { - display: flex; - align-items: center; - box-sizing: border-box; - transition: width 0.2s; - width: 0; -} - -.monaco-list-type-filter.dragging > .controls, -.monaco-list-type-filter:hover > .controls { - width: 36px; -} - -.monaco-list-type-filter > .controls > * { - border: none; - box-sizing: border-box; - -webkit-appearance: none; - -moz-appearance: none; - background: none; - width: 16px; - height: 16px; - flex-shrink: 0; - margin: 0; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; -} - -.monaco-list-type-filter > .controls > .filter { - margin-left: 4px; -} +/* Filter */ .monaco-list-type-filter-message { position: absolute; @@ -149,13 +84,3 @@ .monaco-list-type-filter-message:empty { display: none; } - -/* Electron */ - -.monaco-list-type-filter { - cursor: grab; -} - -.monaco-list-type-filter.dragging { - cursor: grabbing; -} diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 43d4ad49e81..a5f3a920e38 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -12,7 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { IThemable } from 'vs/base/common/styler'; import 'vs/css!./list'; import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list'; -import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List } from './listWidget'; +import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget'; export interface IPagedRenderer extends IListRenderer { renderPlaceholder(index: number, templateData: TTemplateData): void; @@ -38,9 +38,7 @@ class PagedRenderer implements IListRenderer, height: number | undefined): void { - if (data.disposable) { - data.disposable.dispose(); - } + data.disposable?.dispose(); if (!data.data) { return; @@ -95,8 +93,8 @@ class PagedAccessibilityProvider implements IListAccessibilityProvider { - readonly enableKeyboardNavigation?: boolean; - readonly automaticKeyboardNavigation?: boolean; + readonly typeNavigationEnabled?: boolean; + readonly typeNavigationMode?: TypeNavigationMode; readonly ariaLabel?: string; readonly keyboardSupport?: boolean; readonly multipleSelectionSupport?: boolean; @@ -282,8 +280,8 @@ export class PagedList implements IThemable, IDisposable { this.list.layout(height, width); } - toggleKeyboardNavigation(): void { - this.list.toggleKeyboardNavigation(); + triggerTypeNavigation(): void { + this.list.triggerTypeNavigation(); } reveal(index: number, relativeTop?: number): void { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 0f5d1aaa0ea..7e9f2aecba6 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isFirefox } from 'vs/base/browser/browser'; -import { DataTransfers, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd'; +import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { $, addDisposableListener, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -15,7 +15,6 @@ import { Delayer, disposableTimeout } from 'vs/base/common/async'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { getOrDefault } from 'vs/base/common/objects'; import { IRange, Range } from 'vs/base/common/range'; import { INewScrollDimensions, Scrollable, ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; @@ -39,6 +38,10 @@ interface IItem { checkedDisposable: IDisposable; } +const StaticDND = { + CurrentDragAndDropData: undefined as IDragAndDropData | undefined +}; + export interface IListViewDragAndDrop extends IListDragAndDrop { getDragElements(element: T): T[]; } @@ -325,7 +328,7 @@ export class ListView implements ISpliceable, IDisposable { this.domNode.classList.toggle('mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true); - this._horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); + this._horizontalScrolling = options.horizontalScrolling ?? DefaultOptions.horizontalScrolling; this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling); this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight; @@ -335,7 +338,7 @@ export class ListView implements ISpliceable, IDisposable { this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; - const transformOptimization = getOrDefault(options, o => o.transformOptimization, DefaultOptions.transformOptimization); + const transformOptimization = options.transformOptimization ?? DefaultOptions.transformOptimization; if (transformOptimization) { this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)'; } @@ -344,14 +347,14 @@ export class ListView implements ISpliceable, IDisposable { this.scrollable = new Scrollable({ forceIntegerValues: true, - smoothScrollDuration: getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, + smoothScrollDuration: (options.smoothScrolling ?? false) ? 125 : 0, scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb) }); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { - alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel), + alwaysConsumeMouseWheel: options.alwaysConsumeMouseWheel ?? DefaultOptions.alwaysConsumeMouseWheel, horizontal: ScrollbarVisibility.Auto, - vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), - useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows), + vertical: options.verticalScrollMode ?? DefaultOptions.verticalScrollMode, + useShadows: options.useShadows ?? DefaultOptions.useShadows, mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity, fastScrollSensitivity: options.fastScrollSensitivity }, this.scrollable)); @@ -371,10 +374,10 @@ export class ListView implements ISpliceable, IDisposable { this.disposables.add(addDisposableListener(this.domNode, 'dragleave', e => this.onDragLeave(this.toDragEvent(e)))); this.disposables.add(addDisposableListener(this.domNode, 'dragend', e => this.onDragEnd(e))); - this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); - this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight); - this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights); - this.dnd = getOrDefault, IListViewDragAndDrop>(options, o => o.dnd, DefaultOptions.dnd); + this.setRowLineHeight = options.setRowLineHeight ?? DefaultOptions.setRowLineHeight; + this.setRowHeight = options.setRowHeight ?? DefaultOptions.setRowHeight; + this.supportDynamicHeights = options.supportDynamicHeights ?? DefaultOptions.supportDynamicHeights; + this.dnd = options.dnd ?? DefaultOptions.dnd; this.layout(); } @@ -813,9 +816,7 @@ export class ListView implements ISpliceable, IDisposable { throw new Error(`No renderer found for template id ${item.templateId}`); } - if (renderer) { - renderer.renderElement(item.element, index, item.row.templateData, item.size); - } + renderer?.renderElement(item.element, index, item.row.templateData, item.size); const uri = this.dnd.getDragURI(item.element); item.dragStartDisposable.dispose(); @@ -1112,9 +1113,7 @@ export class ListView implements ISpliceable, IDisposable { const item = this.items[index]!; item.dropTarget = true; - if (item.row) { - item.row.domNode.classList.add('drop-target'); - } + item.row?.domNode.classList.add('drop-target'); } this.currentDragFeedbackDisposable = toDisposable(() => { @@ -1122,9 +1121,7 @@ export class ListView implements ISpliceable, IDisposable { const item = this.items[index]!; item.dropTarget = false; - if (item.row) { - item.row.domNode.classList.remove('drop-target'); - } + item.row?.domNode.classList.remove('drop-target'); } }); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 8d509cb4da5..8ed5f2d8602 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { createStyleSheet } from 'vs/base/browser/dom'; -import { DomEmitter, stopEvent } from 'vs/base/browser/event'; +import { createStyleSheet, EventHelper } from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions'; import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays'; @@ -258,6 +259,23 @@ export function isMonacoEditor(e: HTMLElement): boolean { return isMonacoEditor(e.parentElement); } +export function isButton(e: HTMLElement): boolean { + if ((e.tagName === 'A' && e.classList.contains('monaco-button')) || + (e.tagName === 'DIV' && e.classList.contains('monaco-button-dropdown'))) { + return true; + } + + if (e.classList.contains('monaco-list')) { + return false; + } + + if (!e.parentElement) { + return false; + } + + return isButton(e.parentElement); +} + class KeyboardController implements IDisposable { private readonly disposables = new DisposableStore(); @@ -367,7 +385,12 @@ class KeyboardController implements IDisposable { } } -enum TypeLabelControllerState { +export enum TypeNavigationMode { + Automatic, + Trigger +} + +enum TypeNavigationControllerState { Idle, Typing } @@ -385,12 +408,12 @@ export const DefaultKeyboardNavigationDelegate = new class implements IKeyboardN } }; -class TypeLabelController implements IDisposable { +class TypeNavigationController implements IDisposable { private enabled = false; - private state: TypeLabelControllerState = TypeLabelControllerState.Idle; + private state: TypeNavigationControllerState = TypeNavigationControllerState.Idle; - private automaticKeyboardNavigation = true; + private mode = TypeNavigationMode.Automatic; private triggered = false; private previouslyFocused = -1; @@ -401,26 +424,23 @@ class TypeLabelController implements IDisposable { private list: List, private view: ListView, private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, + private keyboardNavigationEventFilter: IKeyboardNavigationEventFilter, private delegate: IKeyboardNavigationDelegate ) { this.updateOptions(list.options); } updateOptions(options: IListOptions): void { - const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation; - - if (enableKeyboardNavigation) { + if (options.typeNavigationEnabled ?? true) { this.enable(); } else { this.disable(); } - if (typeof options.automaticKeyboardNavigation !== 'undefined') { - this.automaticKeyboardNavigation = options.automaticKeyboardNavigation; - } + this.mode = options.typeNavigationMode ?? TypeNavigationMode.Automatic; } - toggle(): void { + trigger(): void { this.triggered = !this.triggered; } @@ -429,12 +449,15 @@ class TypeLabelController implements IDisposable { return; } + let typing = false; + const onChar = this.enabledDisposables.add(Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)) .filter(e => !isInputElement(e.target as HTMLElement)) - .filter(() => this.automaticKeyboardNavigation || this.triggered) + .filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered) .map(event => new StandardKeyboardEvent(event)) + .filter(e => typing || this.keyboardNavigationEventFilter(e)) .filter(e => this.delegate.mightProducePrintableCharacter(e)) - .forEach(e => e.preventDefault()) + .forEach(e => EventHelper.stop(e, true)) .map(event => event.browserEvent.key) .event; @@ -444,6 +467,9 @@ class TypeLabelController implements IDisposable { onInput(this.onInput, this, this.enabledDisposables); onClear(this.onClear, this, this.enabledDisposables); + onChar(() => typing = true, undefined, this.enabledDisposables); + onClear(() => typing = false, undefined, this.enabledDisposables); + this.enabled = true; this.triggered = false; } @@ -473,15 +499,15 @@ class TypeLabelController implements IDisposable { private onInput(word: string | null): void { if (!word) { - this.state = TypeLabelControllerState.Idle; + this.state = TypeNavigationControllerState.Idle; this.triggered = false; return; } const focus = this.list.getFocus(); const start = focus.length > 0 ? focus[0] : 0; - const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0; - this.state = TypeLabelControllerState.Typing; + const delta = this.state === TypeNavigationControllerState.Idle ? 1 : 0; + this.state = TypeNavigationControllerState.Typing; for (let i = 0; i < this.list.length; i++) { const index = (start + i + delta) % this.list.length; @@ -801,6 +827,10 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected .codicon { color: ${styles.listActiveSelectionIconForeground}; }`); } + if (styles.listFocusAndSelectionOutline) { + content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected { outline-color: ${styles.listFocusAndSelectionOutline} !important; }`); + } + if (styles.listFocusAndSelectionBackground) { content.push(` .monaco-drag-image, @@ -874,22 +904,6 @@ export class DefaultStyleController implements IStyleController { `); } - if (styles.listFilterWidgetBackground) { - content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`); - } - - if (styles.listFilterWidgetOutline) { - content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`); - } - - if (styles.listFilterWidgetNoMatchesOutline) { - content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`); - } - - if (styles.listMatchesShadow) { - content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); - } - if (styles.tableColumnsBorder) { content.push(` .monaco-table:hover > .monaco-split-view2, @@ -912,9 +926,13 @@ export class DefaultStyleController implements IStyleController { } } +export interface IKeyboardNavigationEventFilter { + (e: StandardKeyboardEvent): boolean; +} + export interface IListOptionsUpdate extends IListViewOptionsUpdate { - readonly enableKeyboardNavigation?: boolean; - readonly automaticKeyboardNavigation?: boolean; + readonly typeNavigationEnabled?: boolean; + readonly typeNavigationMode?: TypeNavigationMode; readonly multipleSelectionSupport?: boolean; } @@ -927,6 +945,7 @@ export interface IListOptions extends IListOptionsUpdate { readonly multipleSelectionController?: IMultipleSelectionController; readonly styleController?: (suffix: string) => IStyleController; readonly accessibilityProvider?: IListAccessibilityProvider; + readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; // list view options readonly useShadows?: boolean; @@ -943,13 +962,14 @@ export interface IListOptions extends IListOptionsUpdate { readonly alwaysConsumeMouseWheel?: boolean; } -export interface IListStyles { +export interface IListStyles extends IFindInputStyles { listBackground?: Color; listFocusBackground?: Color; listFocusForeground?: Color; listActiveSelectionBackground?: Color; listActiveSelectionForeground?: Color; listActiveSelectionIconForeground?: Color; + listFocusAndSelectionOutline?: Color; listFocusAndSelectionBackground?: Color; listFocusAndSelectionForeground?: Color; listInactiveSelectionBackground?: Color; @@ -967,7 +987,7 @@ export interface IListStyles { listFilterWidgetBackground?: Color; listFilterWidgetOutline?: Color; listFilterWidgetNoMatchesOutline?: Color; - listMatchesShadow?: Color; + listFilterWidgetShadow?: Color; treeIndentGuidesStroke?: Color; tableColumnsBorder?: Color; tableOddRowsBackgroundColor?: Color; @@ -978,6 +998,7 @@ const defaultStyles: IListStyles = { listActiveSelectionBackground: Color.fromHex('#0E639C'), listActiveSelectionForeground: Color.fromHex('#FFFFFF'), listActiveSelectionIconForeground: Color.fromHex('#FFFFFF'), + listFocusAndSelectionOutline: Color.fromHex('#90C2F9'), listFocusAndSelectionBackground: Color.fromHex('#094771'), listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), listInactiveSelectionBackground: Color.fromHex('#3F3F46'), @@ -1224,7 +1245,7 @@ export class List implements ISpliceable, IThemable, IDisposable { protected view: ListView; private spliceable: ISpliceable; private styleController: IStyleController; - private typeLabelController?: TypeLabelController; + private typeNavigationController?: TypeNavigationController; private accessibilityProvider?: IListAccessibilityProvider; private keyboardController: KeyboardController | undefined; private mouseController: MouseController; @@ -1267,7 +1288,7 @@ export class List implements ISpliceable, IThemable, IDisposable { const fromKeyDown = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)) .map(e => new StandardKeyboardEvent(e)) .filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .map(stopEvent) + .map(e => EventHelper.stop(e, true)) .filter(() => false) .event as Event; @@ -1275,7 +1296,7 @@ export class List implements ISpliceable, IThemable, IDisposable { .forEach(() => didJustPressContextMenuKey = false) .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .map(stopEvent) + .map(e => EventHelper.stop(e, true)) .map(({ browserEvent }) => { const focus = this.getFocus(); const index = focus.length ? focus[0] : undefined; @@ -1364,8 +1385,8 @@ export class List implements ISpliceable, IThemable, IDisposable { if (_options.keyboardNavigationLabelProvider) { const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; - this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider, delegate); - this.disposables.add(this.typeLabelController); + this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, _options.keyboardNavigationEventFilter ?? (() => true), delegate); + this.disposables.add(this.typeNavigationController); } this.mouseController = this.createMouseController(_options); @@ -1390,9 +1411,7 @@ export class List implements ISpliceable, IThemable, IDisposable { updateOptions(optionsUpdate: IListOptionsUpdate = {}): void { this._options = { ...this._options, ...optionsUpdate }; - if (this.typeLabelController) { - this.typeLabelController.updateOptions(this._options); - } + this.typeNavigationController?.updateOptions(this._options); if (this._options.multipleSelectionController !== undefined) { if (this._options.multipleSelectionSupport) { @@ -1508,10 +1527,8 @@ export class List implements ISpliceable, IThemable, IDisposable { this.view.layout(height, width); } - toggleKeyboardNavigation(): void { - if (this.typeLabelController) { - this.typeLabelController.toggle(); - } + triggerTypeNavigation(): void { + this.typeNavigationController?.trigger(); } setSelection(indexes: number[], browserEvent?: UIEvent): void { @@ -1778,6 +1795,10 @@ export class List implements ISpliceable, IThemable, IDisposable { return this.view.domNode; } + getElementID(index: number): string { + return this.view.getElementDomId(index); + } + style(styles: IListStyles): void { this.styleController.style(styles); } diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index 319d839e7d7..a5c4b7e07c2 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -15,9 +15,7 @@ export interface IRow { function removeFromParent(element: HTMLElement): void { try { - if (element.parentElement) { - element.parentElement.removeChild(element); - } + element.parentElement?.removeChild(element); } catch (e) { // this will throw if this happens due to a blur event, nasty business } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 224b0cdcb73..e4f1e20ffd9 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -446,7 +446,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { // Set mnemonic if (this.options.label && options.enableMnemonics) { - const label = this.getAction().label; + const label = this.action.label; if (label) { const matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { @@ -552,9 +552,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { override focus(): void { super.focus(); - if (this.item) { - this.item.focus(); - } + this.item?.focus(); this.applyStyle(); } @@ -574,7 +572,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { if (this.options.label) { clearNode(this.label); - let label = stripIcons(this.getAction().label); + let label = stripIcons(this.action.label); if (label) { const cleanLabel = cleanMnemonic(label); if (!this.options.enableMnemonics) { @@ -609,9 +607,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { this.label.innerText = replaceDoubleEscapes(label).trim(); } - if (this.item) { - this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); - } + this.item?.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); } else { this.label.innerText = label.replace(/&&/g, '&').trim(); } @@ -628,7 +624,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { this.item.classList.remove(...this.cssClass.split(' ')); } if (this.options.icon && this.label) { - this.cssClass = this.getAction().class || ''; + this.cssClass = this.action.class || ''; this.label.classList.add('icon'); if (this.cssClass) { this.label.classList.add(...this.cssClass.split(' ')); @@ -640,7 +636,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } override updateEnabled(): void { - if (this.getAction().enabled) { + if (this.action.enabled) { if (this.element) { this.element.classList.remove('disabled'); this.element.removeAttribute('aria-disabled'); @@ -669,7 +665,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { return; } - const checked = this.getAction().checked; + const checked = this.action.checked; this.item.classList.toggle('checked', !!checked); if (checked !== undefined) { this.item.setAttribute('role', 'menuitemcheckbox'); @@ -966,9 +962,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuIndicator.style.color = fgColor ? `${fgColor}` : ''; } - if (this.parentData.submenu) { - this.parentData.submenu.style(this.menuStyle); - } + this.parentData.submenu?.style(this.menuStyle); } override dispose(): void { diff --git a/src/vs/base/browser/ui/menu/menubar.css b/src/vs/base/browser/ui/menu/menubar.css index c4a57a11c0f..3a65f9a8829 100644 --- a/src/vs/base/browser/ui/menu/menubar.css +++ b/src/vs/base/browser/ui/menu/menubar.css @@ -10,9 +10,11 @@ flex-shrink: 1; box-sizing: border-box; height: 100%; - padding: 4px 0; overflow: hidden; - flex-wrap: wrap; +} + +.menubar.overflow-menu-only { + width: 38px; } .fullscreen .menubar:not(.compact) { @@ -21,15 +23,21 @@ } .menubar > .menubar-menu-button { + display: flex; align-items: center; box-sizing: border-box; - padding: 0px 8px; - border-radius: 5px; cursor: default; -webkit-app-region: no-drag; zoom: 1; white-space: nowrap; - outline: 0; + outline: 0 !important; +} + +.monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .menubar-menu-title { + outline-width: 1px; + outline-style: solid; + outline-offset: -1px; + outline-color: var(--vscode-focusBorder); } .menubar.compact { @@ -43,6 +51,11 @@ padding: 0px; } +.menubar-menu-title { + padding: 0px 8px; + border-radius: 5px; +} + .menubar .menubar-menu-items-holder { position: fixed; left: 0px; @@ -64,8 +77,13 @@ } .menubar .toolbar-toggle-more { - width: 20px; - height: 100%; + width: 22px; + height: 22px; + padding: 0 8px; + display: flex; + align-items: center; + justify-content: center; + vertical-align: sub; } .menubar.compact .toolbar-toggle-more { @@ -79,19 +97,15 @@ justify-content: center; } -.menubar .toolbar-toggle-more { - padding: 0; - vertical-align: sub; -} - +.menubar:not(.compact) .menubar-menu-button:first-child .toolbar-toggle-more::before, .menubar.compact .toolbar-toggle-more::before { content: "\eb94" !important; } /* Match behavior of outline for activity bar icons */ -.menubar.compact > .menubar-menu-button.open, -.menubar.compact > .menubar-menu-button:focus, -.menubar.compact > .menubar-menu-button:hover { +.menubar.compact > .menubar-menu-button.open .menubar-menu-title, +.menubar.compact > .menubar-menu-button:focus .menubar-menu-title, +.menubar.compact > .menubar-menu-button:hover .menubar-menu-title{ outline-width: 1px !important; outline-offset: -8px !important; } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 521a65e212e..fa843ff260a 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -313,8 +313,7 @@ export class MenuBar extends Disposable { createOverflowMenu(): void { const label = this.isCompact ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More'); - const title = this.isCompact ? label : undefined; - const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.isCompact ? 0 : -1, 'aria-label': label, 'title': title, 'aria-haspopup': true }); + const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.isCompact ? 0 : -1, 'aria-label': label, 'aria-haspopup': true }); const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + Codicon.menuBarMore.cssSelector, { 'role': 'none', 'aria-hidden': true }); buttonElement.appendChild(titleElement); @@ -330,7 +329,12 @@ export class MenuBar extends Disposable { triggerKeys.push(KeyCode.DownArrow); } else { triggerKeys.push(KeyCode.Space); - triggerKeys.push(this.options.compactMode === Direction.Right ? KeyCode.RightArrow : KeyCode.LeftArrow); + + if (this.options.compactMode === Direction.Right) { + triggerKeys.push(KeyCode.RightArrow); + } else if (this.options.compactMode === Direction.Left) { + triggerKeys.push(KeyCode.LeftArrow); + } } if ((triggerKeys.some(k => event.equals(k)) && !this.isOpen)) { @@ -469,6 +473,11 @@ export class MenuBar extends Disposable { return; } + const overflowMenuOnlyClass = 'overflow-menu-only'; + + // Remove overflow only restriction to allow the most space + this.container.classList.toggle(overflowMenuOnlyClass, false); + const sizeAvailable = this.container.offsetWidth; let currentSize = 0; let full = this.isCompact; @@ -495,6 +504,18 @@ export class MenuBar extends Disposable { } } + + // If below minimium menu threshold, show the overflow menu only as hamburger menu + if (this.numMenusShown - 1 <= showableMenus.length / 2) { + for (const menuBarMenu of showableMenus) { + menuBarMenu.buttonElement.style.visibility = 'hidden'; + } + + full = true; + this.numMenusShown = 0; + currentSize = 0; + } + // Overflow if (this.isCompact) { this.overflowMenu.actions = []; @@ -534,6 +555,9 @@ export class MenuBar extends Disposable { this.container.appendChild(this.overflowMenu.buttonElement); this.overflowMenu.buttonElement.style.visibility = 'hidden'; } + + // If we are only showing the overflow, add this class to avoid taking up space + this.container.classList.toggle(overflowMenuOnlyClass, this.numMenusShown === 0); } private updateLabels(titleElement: HTMLElement, buttonElement: HTMLElement, label: string): void { @@ -740,7 +764,7 @@ export class MenuBar extends Disposable { this._onFocusStateChange.fire(this.focusState >= MenubarState.FOCUSED); } - private get isVisible(): boolean { + get isVisible(): boolean { return this.focusState >= MenubarState.VISIBLE; } @@ -956,16 +980,12 @@ export class MenuBar extends Disposable { } if (this.focusedMenu.holder) { - if (this.focusedMenu.holder.parentElement) { - this.focusedMenu.holder.parentElement.classList.remove('open'); - } + this.focusedMenu.holder.parentElement?.classList.remove('open'); this.focusedMenu.holder.remove(); } - if (this.focusedMenu.widget) { - this.focusedMenu.widget.dispose(); - } + this.focusedMenu.widget?.dispose(); this.focusedMenu = { index: this.focusedMenu.index }; } @@ -975,7 +995,7 @@ export class MenuBar extends Disposable { const actualMenuIndex = menuIndex >= this.numMenusShown ? MenuBar.OVERFLOW_INDEX : menuIndex; const customMenu = actualMenuIndex === MenuBar.OVERFLOW_INDEX ? this.overflowMenu : this.menus[actualMenuIndex]; - if (!customMenu.actions || !customMenu.buttonElement) { + if (!customMenu.actions || !customMenu.buttonElement || !customMenu.titleElement) { return; } @@ -983,18 +1003,19 @@ export class MenuBar extends Disposable { customMenu.buttonElement.classList.add('open'); - const buttonBoundingRect = customMenu.buttonElement.getBoundingClientRect(); + const titleBoundingRect = customMenu.titleElement.getBoundingClientRect(); + const titleBoundingRectZoom = DOM.getDomNodeZoomLevel(customMenu.titleElement); if (this.options.compactMode === Direction.Right) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; - menuHolder.style.left = `${buttonBoundingRect.left + this.container.clientWidth}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; + menuHolder.style.left = `${titleBoundingRect.left + this.container.clientWidth}px`; } else if (this.options.compactMode === Direction.Left) { - menuHolder.style.top = `${buttonBoundingRect.top}px`; + menuHolder.style.top = `${titleBoundingRect.top}px`; menuHolder.style.right = `${this.container.clientWidth}px`; menuHolder.style.left = 'auto'; } else { - menuHolder.style.top = `${buttonBoundingRect.bottom}px`; - menuHolder.style.left = `${buttonBoundingRect.left}px`; + menuHolder.style.top = `${titleBoundingRect.bottom * titleBoundingRectZoom}px`; + menuHolder.style.left = `${titleBoundingRect.left * titleBoundingRectZoom}px`; } customMenu.buttonElement.appendChild(menuHolder); diff --git a/src/vs/editor/contrib/suggest/browser/resizable.ts b/src/vs/base/browser/ui/resizable/resizable.ts similarity index 100% rename from src/vs/editor/contrib/suggest/browser/resizable.ts rename to src/vs/base/browser/ui/resizable/resizable.ts diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 80ab505126e..4bc8ff1b8ee 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, append, createStyleSheet, EventHelper, EventLike, getElementsByTagName } from 'vs/base/browser/dom'; +import { $, append, createStyleSheet, EventHelper, EventLike } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; import { Delayer } from 'vs/base/common/async'; @@ -493,7 +493,7 @@ export class Sash extends Disposable { return; } - const iframes = getElementsByTagName('iframe'); + const iframes = document.getElementsByTagName('iframe'); for (const iframe of iframes) { iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash } diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index f83c34fe519..c7bffd0c8d6 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -232,7 +232,7 @@ export abstract class AbstractScrollableElement extends Widget { this._setListeningToMouseWheel(this._options.handleMouseWheel); this.onmouseover(this._listenOnDomNode, (e) => this._onMouseOver(e)); - this.onnonbubblingmouseout(this._listenOnDomNode, (e) => this._onMouseOut(e)); + this.onmouseleave(this._listenOnDomNode, (e) => this._onMouseLeave(e)); this._hideTimeout = this._register(new TimeoutTimer()); this._isDragging = false; @@ -525,7 +525,7 @@ export abstract class AbstractScrollableElement extends Widget { this._hide(); } - private _onMouseOut(e: IMouseEvent): void { + private _onMouseLeave(e: IMouseEvent): void { this._mouseIsOver = false; this._hide(); } diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts b/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts index 3c4a40b824f..791c7332ffb 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts @@ -103,9 +103,7 @@ export class ScrollbarVisibilityController extends Disposable { // The CSS animation doesn't play otherwise this._revealTimer.setIfNotSet(() => { - if (this._domNode) { - this._domNode.setClassName(this._visibleClassName); - } + this._domNode?.setClassName(this._visibleClassName); }, 0); } @@ -115,8 +113,6 @@ export class ScrollbarVisibilityController extends Disposable { return; } this._isVisible = false; - if (this._domNode) { - this._domNode.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : '')); - } + this._domNode?.setClassName(this._invisibleClassName + (withFadeAway ? ' fade' : '')); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.css b/src/vs/base/browser/ui/selectBox/selectBox.css index d296d1ff0a0..c0a54d83b27 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.css +++ b/src/vs/base/browser/ui/selectBox/selectBox.css @@ -5,6 +5,7 @@ .monaco-select-box { width: 100%; + cursor: pointer; } .monaco-select-box-dropdown-container { diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 55e23987440..fbf46c262fe 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -40,6 +40,7 @@ export interface ISelectBoxDelegate extends IDisposable { export interface ISelectBoxOptions { useCustomDrawn?: boolean; ariaLabel?: string; + ariaDescription?: string; minBottomMargin?: number; optionsAsChildren?: boolean; } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 903700ac0a8..3f571712fdd 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -126,6 +126,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel); } + if (typeof this.selectBoxOptions.ariaDescription === 'string') { + this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription); + } + this._onDidSelect = new Emitter(); this._register(this._onDidSelect); @@ -286,9 +290,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Mirror options in drop-down // Populate select list for non-native select mode - if (this.selectList) { - this.selectList.splice(0, this.selectList.length, this.options); - } + this.selectList?.splice(0, this.selectList.length, this.options); } public select(index: number): void { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 7248cef367e..7a43184de2b 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -35,6 +35,10 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel); } + if (typeof this.selectBoxOptions.ariaDescription === 'string') { + this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription); + } + this._onDidSelect = this._register(new Emitter()); this.styles = styles; diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index 8610b867deb..81f66e998ab 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -24,7 +24,6 @@ height: 22px; font-size: 11px; font-weight: bold; - text-transform: uppercase; overflow: hidden; display: flex; cursor: pointer; @@ -32,6 +31,10 @@ box-sizing: border-box; } +.monaco-pane-view .pane > .pane-header > .title { + text-transform: uppercase; +} + .monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header { flex-direction: column; height: 100%; diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index fc646081f2a..659eb9bd8fd 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -143,9 +143,7 @@ export abstract class Pane extends Disposable implements IView { return false; } - if (this.element) { - this.element.classList.toggle('expanded', expanded); - } + this.element?.classList.toggle('expanded', expanded); this._expanded = !!expanded; this.updateHeader(); diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 57fbf0b1544..159c58a2797 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -52,7 +52,7 @@ export interface IView { readonly minimumSize: number; /** - * A minimum size for this view. + * A maximum size for this view. * * @remarks If none, set it to `Number.POSITIVE_INFINITY`. */ @@ -931,6 +931,23 @@ export class SplitView extends Disposable { this.state = State.Idle; } + /** + * Returns whether all other {@link IView views} are at their minimum size. + */ + isViewSizeMaximized(index: number): boolean { + if (index < 0 || index >= this.viewItems.length) { + return false; + } + + for (const item of this.viewItems) { + if (item !== this.viewItems[index] && item.size > item.minimumSize) { + return false; + } + } + + return true; + } + /** * Distribute the entire {@link SplitView} size among all {@link IView views}. */ diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 03f6f284785..7f1d0560335 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -265,8 +265,8 @@ export class Table implements ISpliceable, IThemable, IDisposable { this.list.layout(listHeight, width); } - toggleKeyboardNavigation(): void { - this.list.toggleKeyboardNavigation(); + triggerTypeNavigation(): void { + this.list.triggerTypeNavigation(); } style(styles: ITableStyles): void { diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 8d2387dbc26..8e7266a2d94 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -148,6 +148,7 @@ export class Toggle extends Widget { this.checked = !this._checked; this._onChange.fire(true); keyboardEvent.preventDefault(); + keyboardEvent.stopPropagation(); return; } diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 63941c5452e..e3c3a56066e 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -130,9 +130,7 @@ export class ToolBar extends Disposable { set context(context: unknown) { this.actionBar.context = context; - if (this.toggleMenuActionViewItem) { - this.toggleMenuActionViewItem.setActionContext(context); - } + this.toggleMenuActionViewItem?.setActionContext(context); for (const actionViewItem of this.submenuActionViewItems) { actionViewItem.setActionContext(context); } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7f96f25f3f2..1032f3e48a9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -3,27 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DragAndDropData, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, append, clearNode, createStyleSheet, getDomNodePagePosition, hasParentWithClass } from 'vs/base/browser/dom'; +import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { $, append, clearNode, createStyleSheet, h, hasParentWithClass } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; -import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; +import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { Action } from 'vs/base/common/actions'; import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays'; -import { disposableTimeout } from 'vs/base/common/async'; +import { disposableTimeout, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { SetMap } from 'vs/base/common/collections'; +import { Color } from 'vs/base/common/color'; import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; -import { isMacintosh } from 'vs/base/common/platform'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; +import { isNumber } from 'vs/base/common/types'; import 'vs/css!./media/tree'; import { localize } from 'vs/nls'; @@ -194,8 +201,7 @@ function asListOptions(modelProvider: () => ITreeModel implements IListRenderer export type LabelFuzzyScore = { label: string; score: FuzzyScore }; -class TypeFilter implements ITreeFilter, IDisposable { +class FindFilter implements ITreeFilter, IDisposable { private _totalCount = 0; get totalCount(): number { return this._totalCount; } private _matchCount = 0; @@ -587,15 +593,11 @@ class TypeFilter implements ITreeFilter, IDi } filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult { + let visibility = TreeVisibility.Visible; + if (this._filter) { const result = this._filter.filter(element, parentVisibility); - if (this.tree.options.simpleKeyboardNavigation) { - return result; - } - - let visibility: TreeVisibility; - if (typeof result === 'boolean') { visibility = result ? TreeVisibility.Visible : TreeVisibility.Hidden; } else if (isFilterResult(result)) { @@ -611,9 +613,9 @@ class TypeFilter implements ITreeFilter, IDi this._totalCount++; - if (this.tree.options.simpleKeyboardNavigation || !this._pattern) { + if (!this._pattern) { this._matchCount++; - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element); @@ -622,22 +624,22 @@ class TypeFilter implements ITreeFilter, IDi for (const l of labels) { const labelStr = l && l.toString(); if (typeof labelStr === 'undefined') { - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); if (score) { this._matchCount++; return labels.length === 1 ? - { data: score, visibility: true } : - { data: { label: labelStr, score: score }, visibility: true }; + { data: score, visibility } : + { data: { label: labelStr, score: score }, visibility }; } } - if (this.tree.options.filterOnType) { + if (this.tree.findMode === TreeFindMode.Filter) { return TreeVisibility.Recurse; } else { - return { data: FuzzyScore.Default, visibility: true }; + return { data: FuzzyScore.Default, visibility }; } } @@ -651,170 +653,282 @@ class TypeFilter implements ITreeFilter, IDi } } -class TypeFilterController implements IDisposable { +export interface ICaseSensitiveToggleOpts { + readonly isChecked: boolean; + readonly inputActiveOptionBorder?: Color; + readonly inputActiveOptionForeground?: Color; + readonly inputActiveOptionBackground?: Color; +} - private _enabled = false; - get enabled(): boolean { return this._enabled; } +export class ModeToggle extends Toggle { + constructor(opts?: ICaseSensitiveToggleOpts) { + super({ + icon: Codicon.filter, + title: localize('filter', "Filter"), + isChecked: opts?.isChecked ?? false, + inputActiveOptionBorder: opts?.inputActiveOptionBorder, + inputActiveOptionForeground: opts?.inputActiveOptionForeground, + inputActiveOptionBackground: opts?.inputActiveOptionBackground + }); + } +} + +export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { } + +export interface IFindWidgetOpts extends IFindWidgetStyles { } + +export enum TreeFindMode { + Highlight, + Filter +} + +class FindWidget extends Disposable { + + private readonly elements = h('.monaco-tree-type-filter', [ + h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab', { tabIndex: 0 }), + h('.monaco-tree-type-filter-input@findInput'), + h('.monaco-tree-type-filter-actionbar@actionbar'), + ]); + + set mode(mode: TreeFindMode) { + this.modeToggle.checked = mode === TreeFindMode.Filter; + this.findInput.inputBox.setPlaceHolder(mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search")); + } + + private readonly modeToggle: ModeToggle; + private readonly findInput: FindInput; + private readonly actionbar: ActionBar; + private width = 0; + private right = 0; + + readonly _onDidDisable = new Emitter(); + readonly onDidDisable = this._onDidDisable.event; + readonly onDidChangeValue: Event; + readonly onDidChangeMode: Event; + + constructor( + container: HTMLElement, + private tree: AbstractTree, + contextViewProvider: IContextViewProvider, + mode: TreeFindMode, + options?: IFindWidgetOpts + ) { + super(); + + container.appendChild(this.elements.root); + this._register(toDisposable(() => container.removeChild(this.elements.root))); + + this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter })); + this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); + + this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, { + label: localize('type to search', "Type to search"), + additionalToggles: [this.modeToggle] + })); + + this.actionbar = this._register(new ActionBar(this.elements.actionbar)); + this.mode = mode; + + const emitter = this._register(new DomEmitter(this.findInput.inputBox.inputElement, 'keydown')); + const onKeyDown = this._register(Event.chain(emitter.event)) + .map(e => new StandardKeyboardEvent(e)) + .event; + + this._register(onKeyDown((e): any => { + switch (e.keyCode) { + case KeyCode.DownArrow: + e.preventDefault(); + e.stopPropagation(); + this.tree.domFocus(); + return; + } + })); + + const closeAction = this._register(new Action('close', localize('close', "Close"), 'codicon codicon-close', true, () => this.dispose())); + this.actionbar.push(closeAction, { icon: true, label: false }); + + const onGrabMouseDown = this._register(new DomEmitter(this.elements.grab, 'mousedown')); + + this._register(onGrabMouseDown.event(e => { + const disposables = new DisposableStore(); + const onWindowMouseMove = disposables.add(new DomEmitter(window, 'mousemove')); + const onWindowMouseUp = disposables.add(new DomEmitter(window, 'mouseup')); + + const startRight = this.right; + const startX = e.pageX; + this.elements.grab.classList.add('grabbing'); + + const update = (e: MouseEvent) => { + const deltaX = e.pageX - startX; + this.right = startRight - deltaX; + this.layout(); + }; + + disposables.add(onWindowMouseMove.event(update)); + disposables.add(onWindowMouseUp.event(e => { + update(e); + this.elements.grab.classList.remove('grabbing'); + disposables.dispose(); + })); + })); + + const onGrabKeyDown = this._register(Event.chain(this._register(new DomEmitter(this.elements.grab, 'keydown')).event)) + .map(e => new StandardKeyboardEvent(e)) + .event; + + this._register(onGrabKeyDown((e): any => { + let right: number | undefined; + + if (e.keyCode === KeyCode.LeftArrow) { + right = Number.POSITIVE_INFINITY; + } else if (e.keyCode === KeyCode.RightArrow) { + right = 0; + } else if (e.keyCode === KeyCode.Space) { + right = this.right === 0 ? Number.POSITIVE_INFINITY : 0; + } + + if (right !== undefined) { + e.preventDefault(); + e.stopPropagation(); + this.right = right; + this.layout(); + } + })); + + this.onDidChangeValue = this.findInput.onDidChange; + this.style(options ?? {}); + } + + style(styles: IFindWidgetStyles): void { + this.findInput.style(styles); + + if (styles.listFilterWidgetBackground) { + this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString(); + } + + if (styles.listFilterWidgetShadow) { + this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; + } + } + + focus() { + this.findInput.focus(); + } + + select() { + this.findInput.select(); + } + + layout(width: number = this.width): void { + this.width = width; + this.right = clamp(this.right, 0, Math.max(0, width - 212)); + this.elements.root.style.right = `${this.right}px`; + } + + showMessage(message: IMessage): void { + this.findInput.showMessage(message); + } + + clearMessage(): void { + this.findInput.clearMessage(); + } + + override async dispose(): Promise { + this._onDidDisable.fire(); + this.elements.root.classList.add('disabled'); + await timeout(300); + super.dispose(); + } +} + +class FindController implements IDisposable { private _pattern = ''; get pattern(): string { return this._pattern; } - private _filterOnType: boolean; - get filterOnType(): boolean { return this._filterOnType; } + private _mode: TreeFindMode; + get mode(): TreeFindMode { return this._mode; } + set mode(mode: TreeFindMode) { + if (mode === this._mode) { + return; + } - private _empty: boolean = false; - get empty(): boolean { return this._empty; } + this._mode = mode; - private readonly _onDidChangeEmptyState = new Emitter(); - readonly onDidChangeEmptyState: Event = Event.latch(this._onDidChangeEmptyState.event); + if (this.widget) { + this.widget.mode = this._mode; + } - private positionClassName = 'ne'; - private domNode: HTMLElement; - private messageDomNode: HTMLElement; - private labelDomNode: HTMLElement; - private filterOnTypeDomNode: HTMLInputElement; - private clearDomNode: HTMLElement; - private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; + this.tree.refilter(); + this.render(); + this._onDidChangeMode.fire(mode); + } - private automaticKeyboardNavigation = true; - private triggered = false; + private widget: FindWidget | undefined; + private styles: IFindWidgetStyles | undefined; + private width = 0; + + private readonly _onDidChangeMode = new Emitter(); + readonly onDidChangeMode = this._onDidChangeMode.event; private readonly _onDidChangePattern = new Emitter(); readonly onDidChangePattern = this._onDidChangePattern.event; - private readonly enabledDisposables = new DisposableStore(); + private readonly _onDidChangeOpenState = new Emitter(); + readonly onDidChangeOpenState = this._onDidChangeOpenState.event; + + private enabledDisposables = new DisposableStore(); private readonly disposables = new DisposableStore(); constructor( private tree: AbstractTree, model: ITreeModel, private view: List>, - private filter: TypeFilter, - private keyboardNavigationDelegate: IKeyboardNavigationDelegate + private filter: FindFilter, + private readonly contextViewProvider: IContextViewProvider ) { - this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`); - this.domNode.draggable = true; - this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart())); - - this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`)); - - this.labelDomNode = append(this.domNode, $('span.label')); - const controls = append(this.domNode, $('.controls')); - - this._filterOnType = !!tree.options.filterOnType; - this.filterOnTypeDomNode = append(controls, $('input.filter')); - this.filterOnTypeDomNode.type = 'checkbox'; - this.filterOnTypeDomNode.checked = this._filterOnType; - this.filterOnTypeDomNode.tabIndex = -1; - this.updateFilterOnTypeTitleAndIcon(); - this.disposables.add(addDisposableListener(this.filterOnTypeDomNode, 'input', () => this.onDidChangeFilterOnType())); - - this.clearDomNode = append(controls, $('button.clear' + Codicon.treeFilterClear.cssSelector)); - this.clearDomNode.tabIndex = -1; - this.clearDomNode.title = localize('clear', "Clear"); - - this.keyboardNavigationEventFilter = tree.options.keyboardNavigationEventFilter; - + this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight; model.onDidSplice(this.onDidSpliceModel, this, this.disposables); - this.updateOptions(tree.options); } - updateOptions(options: IAbstractTreeOptions): void { - if (options.simpleKeyboardNavigation) { - this.disable(); - } else { - this.enable(); - } - - if (typeof options.filterOnType !== 'undefined') { - this._filterOnType = !!options.filterOnType; - this.filterOnTypeDomNode.checked = this._filterOnType; - this.updateFilterOnTypeTitleAndIcon(); - } - - if (typeof options.automaticKeyboardNavigation !== 'undefined') { - this.automaticKeyboardNavigation = options.automaticKeyboardNavigation; - } - - this.tree.refilter(); - this.render(); - - if (!this.automaticKeyboardNavigation) { - this.onEventOrInput(''); - } - } - - toggle(): void { - this.triggered = !this.triggered; - - if (!this.triggered) { - this.onEventOrInput(''); - } - } - - private enable(): void { - if (this._enabled) { + open(): void { + if (this.widget) { + this.widget.focus(); + this.widget.select(); return; } - const onRawKeyDown = this.enabledDisposables.add(new DomEmitter(this.view.getHTMLElement(), 'keydown')); - const onKeyDown = Event.chain(onRawKeyDown.event) - .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode) - .filter(e => e.key !== 'Dead' && !/^Media/.test(e.key)) - .map(e => new StandardKeyboardEvent(e)) - .filter(this.keyboardNavigationEventFilter || (() => true)) - .filter(() => this.automaticKeyboardNavigation || this.triggered) - .filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey))) - .forEach(e => { e.stopPropagation(); e.preventDefault(); }) - .event; + this.mode = this.tree.options.defaultFindMode ?? TreeFindMode.Highlight; + this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.styles); + this.enabledDisposables.add(this.widget); - const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click')); + this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables); + this.widget.onDidChangeMode(mode => this.mode = mode, undefined, this.enabledDisposables); + this.widget.onDidDisable(this.close, this, this.enabledDisposables); - Event.chain(Event.any(onKeyDown, onClearClick.event)) - .event(this.onEventOrInput, this, this.enabledDisposables); + this.widget.layout(this.width); + this.widget.focus(); - this.filter.pattern = ''; - this.tree.refilter(); - this.render(); - this._enabled = true; - this.triggered = false; + this._onDidChangeOpenState.fire(true); } - private disable(): void { - if (!this._enabled) { + close(): void { + if (!this.widget) { return; } - this.domNode.remove(); - this.enabledDisposables.clear(); - this.tree.refilter(); - this.render(); - this._enabled = false; - this.triggered = false; + this.widget = undefined; + + this.enabledDisposables.dispose(); + this.enabledDisposables = new DisposableStore(); + + this.onDidChangeValue(''); + this.tree.domFocus(); + + this._onDidChangeOpenState.fire(false); } - private onEventOrInput(e: MouseEvent | StandardKeyboardEvent | string): void { - if (typeof e === 'string') { - this.onInput(e); - } else if (e instanceof MouseEvent || e.keyCode === KeyCode.Escape || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey))) { - this.onInput(''); - } else if (e.keyCode === KeyCode.Backspace) { - this.onInput(this.pattern.length === 0 ? '' : this.pattern.substr(0, this.pattern.length - 1)); - } else { - this.onInput(this.pattern + e.browserEvent.key); - } - } - - private onInput(pattern: string): void { - const container = this.view.getHTMLElement(); - - if (pattern && !this.domNode.parentElement) { - container.append(this.domNode); - } else if (!pattern && this.domNode.parentElement) { - this.domNode.remove(); - this.tree.domFocus(); - } - + private onDidChangeValue(pattern: string): void { this._pattern = pattern; this._onDidChangePattern.fire(pattern); @@ -836,75 +950,10 @@ class TypeFilterController implements IDisposable { } this.render(); - - if (!pattern) { - this.triggered = false; - } - } - - private onDragStart(): void { - const container = this.view.getHTMLElement(); - const { left } = getDomNodePagePosition(container); - const containerWidth = container.clientWidth; - const midContainerWidth = containerWidth / 2; - const width = this.domNode.clientWidth; - const disposables = new DisposableStore(); - let positionClassName = this.positionClassName; - - const updatePosition = () => { - switch (positionClassName) { - case 'nw': - this.domNode.style.top = `4px`; - this.domNode.style.left = `4px`; - break; - case 'ne': - this.domNode.style.top = `4px`; - this.domNode.style.left = `${containerWidth - width - 6}px`; - break; - } - }; - - const onDragOver = (event: DragEvent) => { - event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) - - const x = event.clientX - left; - if (event.dataTransfer) { - event.dataTransfer.dropEffect = 'none'; - } - - if (x < midContainerWidth) { - positionClassName = 'nw'; - } else { - positionClassName = 'ne'; - } - - updatePosition(); - }; - - const onDragEnd = () => { - this.positionClassName = positionClassName; - this.domNode.className = `monaco-list-type-filter ${this.positionClassName}`; - this.domNode.style.top = ''; - this.domNode.style.left = ''; - - dispose(disposables); - }; - - updatePosition(); - this.domNode.classList.remove(positionClassName); - - this.domNode.classList.add('dragging'); - disposables.add(toDisposable(() => this.domNode.classList.remove('dragging'))); - - disposables.add(addDisposableListener(document, 'dragover', e => onDragOver(e))); - disposables.add(addDisposableListener(this.domNode, 'dragend', () => onDragEnd())); - - StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui'); - disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined)); } private onDidSpliceModel(): void { - if (!this._enabled || this.pattern.length === 0) { + if (!this.widget || this.pattern.length === 0) { return; } @@ -912,46 +961,18 @@ class TypeFilterController implements IDisposable { this.render(); } - private onDidChangeFilterOnType(): void { - this.tree.updateOptions({ filterOnType: this.filterOnTypeDomNode.checked }); - this.tree.refilter(); - this.tree.domFocus(); - this.render(); - this.updateFilterOnTypeTitleAndIcon(); - } - - private updateFilterOnTypeTitleAndIcon(): void { - if (this.filterOnType) { - this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOff.classNamesArray); - this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOn.classNamesArray); - this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type"); - } else { - this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOn.classNamesArray); - this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOff.classNamesArray); - this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type"); - } - } - private render(): void { const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0; - if (this.pattern && this.tree.options.filterOnType && noMatches) { - this.messageDomNode.textContent = localize('empty', "No elements found"); - this._empty = true; + if (this.pattern && noMatches) { + this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") }); } else { - this.messageDomNode.innerText = ''; - this._empty = false; + this.widget?.clearMessage(); } - - this.domNode.classList.toggle('no-matches', noMatches); - this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount); - this.labelDomNode.textContent = this.pattern.length > 16 ? 'â€Ļ' + this.pattern.substr(this.pattern.length - 16) : this.pattern; - - this._onDidChangeEmptyState.fire(this._empty); } shouldAllowFocus(node: ITreeNode): boolean { - if (!this.enabled || !this.pattern || this.filterOnType) { + if (!this.widget || !this.pattern || this._mode === TreeFindMode.Filter) { return true; } @@ -962,16 +983,20 @@ class TypeFilterController implements IDisposable { return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore); } - dispose() { - if (this._enabled) { - this.domNode.remove(); - this.enabledDisposables.dispose(); - this._enabled = false; - this.triggered = false; - } + style(styles: IFindWidgetStyles): void { + this.styles = styles; + this.widget?.style(styles); + } + layout(width: number): void { + this.width = width; + this.widget?.layout(width); + } + + dispose() { this._onDidChangePattern.dispose(); - dispose(this.disposables); + this.enabledDisposables.dispose(); + this.disposables.dispose(); } } @@ -982,6 +1007,8 @@ function asTreeMouseEvent(event: IListMouseEvent>): ITreeMo target = TreeMouseEventTarget.Twistie; } else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-contents', 'monaco-tl-row')) { target = TreeMouseEventTarget.Element; + } else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tree-type-filter', 'monaco-list')) { + target = TreeMouseEventTarget.Filter; } return { @@ -999,15 +1026,11 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends IAbstractTreeOptionsUpdate, IListOptions { + readonly contextViewProvider?: IContextViewProvider; readonly collapseByDefault?: boolean; // defaults to false readonly filter?: ITreeFilter; readonly dnd?: ITreeDragAndDrop; - readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; readonly additionalScrollHeight?: number; + readonly findWidgetEnabled?: boolean; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { @@ -1153,7 +1177,9 @@ class TreeNodeListMouseController extends MouseController< } protected override onViewPointer(e: IListMouseEvent>): void { - if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isButton(e.browserEvent.target as HTMLElement) || + isInputElement(e.browserEvent.target as HTMLElement) || + isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -1316,7 +1342,8 @@ export abstract class AbstractTree implements IDisposable private selection: Trait; private anchor: Trait; private eventBufferer = new EventBufferer(); - private typeFilterController?: TypeFilterController; + private findController?: FindController; + readonly onDidChangeFindOpenState: Event = Event.None; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; private styleElement: HTMLStyleElement; protected readonly disposables = new DisposableStore(); @@ -1327,7 +1354,7 @@ export abstract class AbstractTree implements IDisposable get onDidChangeSelection(): Event> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); } get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } - get onMouseDblClick(): Event> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); } + get onMouseDblClick(): Event> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); } get onContextMenu(): Event> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); } get onTap(): Event> { return Event.map(this.view.onTap, asTreeMouseEvent); } get onPointer(): Event> { return Event.map(this.view.onPointer, asTreeMouseEvent); } @@ -1346,8 +1373,11 @@ export abstract class AbstractTree implements IDisposable private readonly _onWillRefilter = new Emitter(); readonly onWillRefilter: Event = this._onWillRefilter.event; - get filterOnType(): boolean { return !!this._options.filterOnType; } - get onDidChangeTypeFilterPattern(): Event { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; } + get findMode(): TreeFindMode { return this.findController?.mode ?? TreeFindMode.Highlight; } + set findMode(findMode: TreeFindMode) { if (this.findController) { this.findController.mode = findMode; } } + readonly onDidChangeFindMode: Event; + + get onDidChangeFindPattern(): Event { return this.findController ? this.findController.onDidChangePattern : Event.None; } get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; } get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; } @@ -1374,10 +1404,10 @@ export abstract class AbstractTree implements IDisposable this.disposables.add(r); } - let filter: TypeFilter | undefined; + let filter: FindFilter | undefined; if (_options.keyboardNavigationLabelProvider) { - filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter); + filter = new FindFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter); _options = { ..._options, filter: filter as ITreeFilter }; // TODO need typescript help here this.disposables.add(filter); } @@ -1430,11 +1460,14 @@ export abstract class AbstractTree implements IDisposable onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables); } - if (_options.keyboardNavigationLabelProvider) { - const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; - this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate); - this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node); - this.disposables.add(this.typeFilterController!); + if ((_options.findWidgetEnabled ?? true) && _options.keyboardNavigationLabelProvider && _options.contextViewProvider) { + this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider); + this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node); + this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; + this.disposables.add(this.findController!); + this.onDidChangeFindMode = this.findController.onDidChangeMode; + } else { + this.onDidChangeFindMode = Event.None; } this.styleElement = createStyleSheet(this.view.getHTMLElement()); @@ -1448,15 +1481,7 @@ export abstract class AbstractTree implements IDisposable renderer.updateOptions(optionsUpdate); } - this.view.updateOptions({ - ...this._options, - enableKeyboardNavigation: this._options.simpleKeyboardNavigation, - }); - - if (this.typeFilterController) { - this.typeFilterController.updateOptions(this._options); - } - + this.view.updateOptions(this._options); this._onDidUpdateOptions.fire(this._options); this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always); @@ -1483,21 +1508,11 @@ export abstract class AbstractTree implements IDisposable } get contentHeight(): number { - if (this.typeFilterController && this.typeFilterController.filterOnType && this.typeFilterController.empty) { - return 100; - } - return this.view.contentHeight; } get onDidChangeContentHeight(): Event { - let result = this.view.onDidChangeContentHeight; - - if (this.typeFilterController) { - result = Event.any(result, Event.map(this.typeFilterController.onDidChangeEmptyState, () => this.contentHeight)); - } - - return result; + return this.view.onDidChangeContentHeight; } get scrollTop(): number { @@ -1559,6 +1574,10 @@ export abstract class AbstractTree implements IDisposable layout(height?: number, width?: number): void { this.view.layout(height, width); + + if (isNumber(width)) { + this.findController?.layout(width); + } } style(styles: IListStyles): void { @@ -1571,6 +1590,8 @@ export abstract class AbstractTree implements IDisposable } this.styleElement.textContent = content.join('\n'); + + this.findController?.style(styles); this.view.style(styles); } @@ -1624,12 +1645,16 @@ export abstract class AbstractTree implements IDisposable return this.model.isCollapsed(location); } - toggleKeyboardNavigation(): void { - this.view.toggleKeyboardNavigation(); + triggerTypeNavigation(): void { + this.view.triggerTypeNavigation(); + } - if (this.typeFilterController) { - this.typeFilterController.toggle(); - } + openFind(): void { + this.findController?.open(); + } + + closeFind(): void { + this.findController?.close(); } refilter(): void { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index fbd7ad55081..a8b84679303 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -7,7 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; +import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; @@ -341,7 +341,12 @@ export class AsyncDataTree implements IDisposable get onDidUpdateOptions(): Event { return this.tree.onDidUpdateOptions; } - get filterOnType(): boolean { return this.tree.filterOnType; } + get onDidChangeFindOpenState(): Event { return this.tree.onDidChangeFindOpenState; } + + get findMode(): TreeFindMode { return this.tree.findMode; } + set findMode(mode: TreeFindMode) { this.tree.findMode = mode; } + readonly onDidChangeFindMode: Event; + get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') { return this.tree.expandOnlyOnTwistieClick; @@ -367,6 +372,7 @@ export class AsyncDataTree implements IDisposable this.collapseByDefault = options.collapseByDefault; this.tree = this.createTree(user, container, delegate, renderers, options); + this.onDidChangeFindMode = this.tree.onDidChangeFindMode; this.root = createAsyncDataTreeNode({ element: undefined!, @@ -616,8 +622,16 @@ export class AsyncDataTree implements IDisposable return this.tree.isCollapsed(this.getDataNode(element)); } - toggleKeyboardNavigation(): void { - this.tree.toggleKeyboardNavigation(); + triggerTypeNavigation(): void { + this.tree.triggerTypeNavigation(); + } + + openFind(): void { + this.tree.openFind(); + } + + closeFind(): void { + this.tree.closeFind(); } refilter(): void { @@ -728,6 +742,16 @@ export class AsyncDataTree implements IDisposable return result; } + if (node !== this.root) { + const treeNode = this.tree.getNode(node); + + if (treeNode.collapsed) { + node.hasChildren = !!this.dataSource.hasChildren(node.element!); + node.stale = true; + return; + } + } + return this.doRefreshSubTree(node, recursive, viewStateContext); } @@ -1179,10 +1203,10 @@ export class CompressibleAsyncDataTree extends As const expanded: string[] = []; const root = this.tree.getCompressedTreeNode(); - const queue = [root]; + const stack = [root]; - while (queue.length > 0) { - const node = queue.shift()!; + while (stack.length > 0) { + const node = stack.pop()!; if (node !== root && node.collapsible && !node.collapsed) { for (const asyncNode of node.element!.elements) { @@ -1190,7 +1214,7 @@ export class CompressibleAsyncDataTree extends As } } - queue.push(...node.children); + stack.push(...node.children); } return { focus, selection, expanded, scrollTop: this.scrollTop }; diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 22f0d23bf60..bee620d57ef 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -10,6 +10,9 @@ position: relative; } +.monaco-tl-row.disabled { + cursor: default; +} .monaco-tl-indent { height: 100%; position: absolute; @@ -67,3 +70,55 @@ /* Use steps to throttle FPS to reduce CPU usage */ animation: codicon-spin 1.25s steps(30) infinite; } + +.monaco-tree-type-filter { + position: absolute; + top: 0; + display: flex; + padding: 3px; + transition: top 0.3s; + max-width: 200px; + z-index: 100; + margin: 0 6px; +} + +.monaco-tree-type-filter.disabled { + top: -40px; +} + +.monaco-tree-type-filter-grab { + display: flex !important; + align-items: center; + justify-content: center; + cursor: grab; + margin-right: 2px; +} + +.monaco-tree-type-filter-grab.grabbing { + cursor: grabbing; +} + +.monaco-tree-type-filter-input { + flex: 1; +} + +.monaco-tree-type-filter-input .monaco-inputbox { + height: 23px; +} + +.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input, +.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror { + padding: 2px 4px; +} + +.monaco-tree-type-filter-input .monaco-findInput > .controls { + top: 2px; +} + +.monaco-tree-type-filter-actionbar { + margin-left: 4px; +} + +.monaco-tree-type-filter-actionbar .monaco-action-bar .action-label { + padding: 2px; +} diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 0166e4cd3b6..f29091c104a 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -141,7 +141,8 @@ export interface ITreeEvent { export enum TreeMouseEventTarget { Unknown, Twistie, - Element + Element, + Filter } export interface ITreeMouseEvent { diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts index 3b17f5bce8f..5a994a6f4be 100644 --- a/src/vs/base/browser/ui/widget.ts +++ b/src/vs/base/browser/ui/widget.ts @@ -23,8 +23,8 @@ export abstract class Widget extends Disposable { this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_OVER, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); } - protected onnonbubblingmouseout(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { - this._register(dom.addDisposableNonBubblingMouseOutListener(domNode, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); + protected onmouseleave(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { + this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_LEAVE, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); } protected onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void { diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index f9729a18770..ba45db870fe 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -15,8 +15,9 @@ export interface ITelemetryData { export type WorkbenchActionExecutedClassification = { owner: 'bpasero'; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'TODO @bpasero'; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' }; + from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the component the action was run from.' }; }; export type WorkbenchActionExecutedEvent = { @@ -24,7 +25,7 @@ export type WorkbenchActionExecutedEvent = { from: string; }; -export interface IAction extends IDisposable { +export interface IAction { readonly id: string; label: string; tooltip: string; @@ -241,12 +242,6 @@ export class SubmenuAction implements IAction { this._actions = actions; } - dispose(): void { - // there is NOTHING to dispose and the SubmenuAction should - // never have anything to dispose as it is a convenience type - // to bridge into the rendering world. - } - async run(): Promise { } } @@ -267,7 +262,6 @@ export function toAction(props: { id: string; label: string; enabled?: boolean; enabled: props.enabled ?? true, checked: props.checked ?? false, run: async () => props.run(), - tooltip: props.label, - dispose: () => { } + tooltip: props.label }; } diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index cc567305a59..2457de74f0b 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -46,6 +46,18 @@ export function equals(one: ReadonlyArray | undefined, other: ReadonlyArra return true; } +/** + * Remove the element at `index` by replacing it with the last element. This is faster than `splice` + * but changes the order of the array + */ +export function removeFastWithoutKeepingOrder(array: T[], index: number) { + const last = array.length - 1; + if (index < last) { + array[index] = array[last]; + } + array.pop(); +} + /** * Performs a binary search algorithm over a sorted array. * @@ -677,6 +689,18 @@ export function compareBy(selector: (item: TItem) => TCompare return (a, b) => comparator(selector(a), selector(b)); } +export function tieBreakComparators(...comparators: Comparator[]): Comparator { + return (item1, item2) => { + for (const comparator of comparators) { + const result = comparator(item1, item2); + if (!CompareResult.isNeitherLessOrGreaterThan(result)) { + return result; + } + } + return CompareResult.neitherLessOrGreaterThan; + }; +} + /** * The natural order on numbers. */ @@ -805,3 +829,79 @@ export class ArrayQueue { return result; } } + +/** + * This class is faster than an iterator and array for lazy computed data. +*/ +export class CallbackIterable { + public static readonly empty = new CallbackIterable(_callback => { }); + + constructor( + /** + * Calls the callback for every item. + * Stops when the callback returns false. + */ + public readonly iterate: (callback: (item: T) => boolean) => void + ) { + } + + forEach(handler: (item: T) => void) { + this.iterate(item => { handler(item); return true; }); + } + + toArray(): T[] { + const result: T[] = []; + this.iterate(item => { result.push(item); return true; }); + return result; + } + + filter(predicate: (item: T) => boolean): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true)); + } + + map(mapFn: (item: T) => TResult): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => cb(mapFn(item)))); + } + + some(predicate: (item: T) => boolean): boolean { + let result = false; + this.iterate(item => { result = predicate(item); return !result; }); + return result; + } + + findFirst(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + return false; + } + return true; + }); + return result; + } + + findLast(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + } + return true; + }); + return result; + } + + findLastMaxBy(comparator: Comparator): T | undefined { + let result: T | undefined; + let first = true; + this.iterate(item => { + if (first || CompareResult.isGreaterThan(comparator(item, result!))) { + first = false; + result = item; + } + return true; + }); + return result; + } +} diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index 9e2510b4d0b..04d57c2ff87 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -11,3 +11,7 @@ export function ok(value?: unknown, message?: string) { throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed'); } } + +export function assertNever(value: never, message = 'Unreachable'): never { + throw new Error(message); +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index e45c78ae5e3..562710d4c84 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -1389,7 +1389,7 @@ export class DeferredPromise { return this.rejected || this.resolved; } - public p: Promise; + public readonly p: Promise; constructor() { this.p = new Promise((c, e) => { diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index 9cc02257cab..78e01b75ec1 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -129,9 +129,7 @@ export class CancellationTokenSource { if (cancel) { this.cancel(); } - if (this._parentListener) { - this._parentListener.dispose(); - } + this._parentListener?.dispose(); if (!this._token) { // ensure to initialize with an empty token if we had none this._token = CancellationToken.None; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 7021acc29ef..17119e4b867 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -94,11 +94,12 @@ export class Codicon implements CSSIcon { public static readonly eyeUnwatch = new Codicon('eye-unwatch', { fontCharacter: '\\ea70' }); public static readonly eyeWatch = new Codicon('eye-watch', { fontCharacter: '\\ea70' }); public static readonly circleFilled = new Codicon('circle-filled', { fontCharacter: '\\ea71' }); - public static readonly primitiveDot = new Codicon('primitive-dot', { fontCharacter: '\\ea71' }); - public static readonly closeDirty = new Codicon('close-dirty', { fontCharacter: '\\ea71' }); - public static readonly debugBreakpoint = new Codicon('debug-breakpoint', { fontCharacter: '\\ea71' }); - public static readonly debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', { fontCharacter: '\\ea71' }); - public static readonly debugHint = new Codicon('debug-hint', { fontCharacter: '\\ea71' }); + public static readonly primitiveDot = new Codicon('primitive-dot', Codicon.circleFilled.definition); + public static readonly closeDirty = new Codicon('close-dirty', Codicon.circleFilled.definition); + public static readonly terminalDecorationSuccess = new Codicon('terminal-decoration-success', Codicon.circleFilled.definition); + public static readonly debugBreakpoint = new Codicon('debug-breakpoint', Codicon.circleFilled.definition); + public static readonly debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', Codicon.circleFilled.definition); + public static readonly debugHint = new Codicon('debug-hint', Codicon.circleFilled.definition); public static readonly primitiveSquare = new Codicon('primitive-square', { fontCharacter: '\\ea72' }); public static readonly edit = new Codicon('edit', { fontCharacter: '\\ea73' }); public static readonly pencil = new Codicon('pencil', { fontCharacter: '\\ea73' }); @@ -218,8 +219,10 @@ export class Codicon implements CSSIcon { public static readonly chromeMaximize = new Codicon('chrome-maximize', { fontCharacter: '\\eab9' }); public static readonly chromeMinimize = new Codicon('chrome-minimize', { fontCharacter: '\\eaba' }); public static readonly chromeRestore = new Codicon('chrome-restore', { fontCharacter: '\\eabb' }); - public static readonly circleOutline = new Codicon('circle-outline', { fontCharacter: '\\eabc' }); - public static readonly debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', { fontCharacter: '\\eabc' }); + public static readonly circle = new Codicon('circle', { fontCharacter: '\\eabc' }); + public static readonly circleOutline = new Codicon('circle-outline', Codicon.circle.definition); + public static readonly terminalDecorationIncomplete = new Codicon('terminal-decoration-incomplete', Codicon.circle.definition); + public static readonly debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', Codicon.circle.definition); public static readonly circleSlash = new Codicon('circle-slash', { fontCharacter: '\\eabd' }); public static readonly circuitBoard = new Codicon('circuit-board', { fontCharacter: '\\eabe' }); public static readonly clearAll = new Codicon('clear-all', { fontCharacter: '\\eabf' }); @@ -428,7 +431,9 @@ export class Codicon implements CSSIcon { public static readonly debugBreakpointFunction = new Codicon('debug-breakpoint-function', { fontCharacter: '\\eb88' }); public static readonly debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { fontCharacter: '\\eb88' }); public static readonly debugStackframeActive = new Codicon('debug-stackframe-active', { fontCharacter: '\\eb89' }); - public static readonly debugStackframeDot = new Codicon('debug-stackframe-dot', { fontCharacter: '\\eb8a' }); + public static readonly circleSmallFilled = new Codicon('circle-small-filled', { fontCharacter: '\\eb8a' }); + public static readonly debugStackframeDot = new Codicon('debug-stackframe-dot', Codicon.circleSmallFilled.definition); + public static readonly terminalDecorationMark = new Codicon('terminal-decoration-mark', Codicon.circleSmallFilled.definition); public static readonly debugStackframe = new Codicon('debug-stackframe', { fontCharacter: '\\eb8b' }); public static readonly debugStackframeFocused = new Codicon('debug-stackframe-focused', { fontCharacter: '\\eb8b' }); public static readonly debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { fontCharacter: '\\eb8c' }); @@ -472,7 +477,8 @@ export class Codicon implements CSSIcon { public static readonly pinnedDirty = new Codicon('pinned-dirty', { fontCharacter: '\\ebb2' }); public static readonly passFilled = new Codicon('pass-filled', { fontCharacter: '\\ebb3' }); public static readonly circleLargeFilled = new Codicon('circle-large-filled', { fontCharacter: '\\ebb4' }); - public static readonly circleLargeOutline = new Codicon('circle-large-outline', { fontCharacter: '\\ebb5' }); + public static readonly circleLarge = new Codicon('circle-large', { fontCharacter: '\\ebb5' }); + public static readonly circleLargeOutline = new Codicon('circle-large-outline', Codicon.circleLarge.definition); public static readonly combine = new Codicon('combine', { fontCharacter: '\\ebb6' }); public static readonly gather = new Codicon('gather', { fontCharacter: '\\ebb6' }); public static readonly table = new Codicon('table', { fontCharacter: '\\ebb7' }); @@ -548,11 +554,21 @@ export class Codicon implements CSSIcon { public static readonly indent = new Codicon('indent', { fontCharacter: '\\ebf9' }); public static readonly recordSmall = new Codicon('record-small', { fontCharacter: '\\ebfa' }); public static readonly errorSmall = new Codicon('error-small', { fontCharacter: '\\ebfb' }); + public static readonly terminalDecorationError = new Codicon('terminal-decoration-error', Codicon.errorSmall.definition); public static readonly arrowCircleDown = new Codicon('arrow-circle-down', { fontCharacter: '\\ebfc' }); public static readonly arrowCircleLeft = new Codicon('arrow-circle-left', { fontCharacter: '\\ebfd' }); public static readonly arrowCircleRight = new Codicon('arrow-circle-right', { fontCharacter: '\\ebfe' }); public static readonly arrowCircleUp = new Codicon('arrow-circle-up', { fontCharacter: '\\ebff' }); public static readonly heartFilled = new Codicon('heart-filled', { fontCharacter: '\\ec04' }); + public static readonly map = new Codicon('map', { fontCharacter: '\\ec05' }); + public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' }); + public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' }); + public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' }); + public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\ec09' }); + public static readonly commentUnresolved = new Codicon('comment-unresolved', { fontCharacter: '\\ec0a' }); + public static readonly gitPullRequestGoToChanges = new Codicon('git-pull-request-go-to-changes', { fontCharacter: '\\ec0b' }); + public static readonly gitPullRequestNewChanges = new Codicon('git-pull-request-new-changes', { fontCharacter: '\\ec0c' }); + public static readonly searchFuzzy = new Codicon('search-fuzzy', { fontCharacter: '\\ec0d' }); // derived icons, that could become separate icons diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index b3435048211..d8ee92f757e 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -9,46 +9,12 @@ */ export type IStringDictionary = Record; - /** * An interface for a JavaScript object that * acts a dictionary. The keys are numbers. */ export type INumberDictionary = Record; -const hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * Returns an array which contains all values that reside - * in the given dictionary. - */ -export function values(from: IStringDictionary | INumberDictionary): T[] { - const result: T[] = []; - for (const key in from) { - if (hasOwnProperty.call(from, key)) { - result.push((from as any)[key]); - } - } - return result; -} - -/** - * Iterates over each entry in the provided dictionary. The iterator allows - * to remove elements and will stop when the callback returns {{false}}. - */ -export function forEach(from: IStringDictionary | INumberDictionary, callback: (entry: { key: any; value: T }, remove: () => void) => any): void { - for (const key in from) { - if (hasOwnProperty.call(from, key)) { - const result = callback({ key: key, value: (from as any)[key] }, function () { - delete (from as any)[key]; - }); - if (result === false) { - return; - } - } - } -} - /** * Groups the collection into a dictionary based on the provided * group function. @@ -66,16 +32,6 @@ export function groupBy(data: V[], groupF return result; } -export function fromMap(original: Map): IStringDictionary { - const result: IStringDictionary = Object.create(null); - if (original) { - original.forEach((value, key) => { - result[key] = value; - }); - } - return result; -} - export function diffSets(before: Set, after: Set): { removed: T[]; added: T[] } { const removed: T[] = []; const added: T[] = []; diff --git a/src/vs/base/common/console.ts b/src/vs/base/common/console.ts index 194abde45b5..54bfadefbae 100644 --- a/src/vs/base/common/console.ts +++ b/src/vs/base/common/console.ts @@ -11,7 +11,7 @@ export interface IRemoteConsoleLog { arguments: string; } -interface IStackArgument { +export interface IStackArgument { __$stack: string; } diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 9ea8d9f394e..44b25af9e3a 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -243,7 +243,7 @@ export class ErrorNoTelemetry extends Error { constructor(msg?: string) { super(msg); - this.name = 'ErrorNoTelemetry'; + this.name = 'CodeExpectedError'; } public static fromError(err: Error): ErrorNoTelemetry { @@ -258,7 +258,7 @@ export class ErrorNoTelemetry extends Error { } public static isErrorNoTelemetry(err: Error): err is ErrorNoTelemetry { - return err.name === 'ErrorNoTelemetry'; + return err.name === 'CodeExpectedError'; } } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index bd8ef3bbedb..5ceab5b92a0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IObservable, IObserver } from 'vs/base/common/observable'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -160,9 +161,7 @@ export namespace Event { const emitter = new Emitter(options); - if (disposable) { - disposable.add(emitter); - } + disposable?.add(emitter); return emitter.event; } @@ -223,9 +222,7 @@ export namespace Event { const emitter = new Emitter(options); - if (disposable) { - disposable.add(emitter); - } + disposable?.add(emitter); return emitter.event; } @@ -276,9 +273,7 @@ export namespace Event { }); const flush = () => { - if (buffer) { - buffer.forEach(e => emitter.fire(e)); - } + buffer?.forEach(e => emitter.fire(e)); buffer = null; }; @@ -429,6 +424,55 @@ export namespace Event { store?.dispose(); }); } + + class EmitterObserver implements IObserver { + + readonly emitter: Emitter; + + private _counter = 0; + private _hasChanged = false; + + constructor(readonly obs: IObservable, store: DisposableStore | undefined) { + const options = { + onFirstListenerAdd: () => { + obs.addObserver(this); + }, + onLastListenerRemove: () => { + obs.removeObserver(this); + } + }; + if (!store) { + _addLeakageTraceLogic(options); + } + this.emitter = new Emitter(options); + if (store) { + store.add(this.emitter); + } + } + + beginUpdate(_observable: IObservable): void { + // console.assert(_observable === this.obs); + this._counter++; + } + + handleChange(_observable: IObservable, _change: TChange): void { + this._hasChanged = true; + } + + endUpdate(_observable: IObservable): void { + if (--this._counter === 0) { + if (this._hasChanged) { + this._hasChanged = false; + this.emitter.fire(this.obs.get()); + } + } + } + } + + export function fromObservable(obs: IObservable, store?: DisposableStore): Event { + const observer = new EmitterObserver(obs, store); + return observer.emitter.event; + } } export interface EmitterOptions { diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index d0a8a09aa70..ebad3f5805d 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -316,7 +316,18 @@ function _matchesWords(word: string, target: string, i: number, j: number, conti nextWordIndex++; } } - return result === null ? null : join({ start: j, end: j + 1 }, result); + + if (!result) { + return null; + } + + // If the characters don't exactly match, then they must be word separators (see charactersMatch(...)). + // We don't want to include this in the matches but we don't want to throw the target out all together so we return `result`. + if (word.charCodeAt(i) !== target.charCodeAt(j)) { + return result; + } + + return join({ start: j, end: j + 1 }, result); } } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index ab2458bc061..ae43227f6e2 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -156,6 +156,14 @@ export function dispose(arg: T | IterableIterator | un } } +export function disposeIfDisposable(disposables: Array): Array { + for (const d of disposables) { + if (isDisposable(d)) { + d.dispose(); + } + } + return []; +} export function combinedDisposable(...disposables: IDisposable[]): IDisposable { const parent = toDisposable(() => dispose(disposables)); diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 4b153336f01..2a78f7afb18 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -715,22 +715,28 @@ export class TernarySearchTree { yield* this._entries(this._root); } - private *_entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + private _entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + const result: [K, V][] = []; + this._dfsEntries(node, result); + return result[Symbol.iterator](); + } + + private _dfsEntries(node: TernarySearchTreeNode | undefined, bucket: [K, V][]) { // DFS if (!node) { return; } if (node.left) { - yield* this._entries(node.left); + this._dfsEntries(node.left, bucket); } if (node.value) { - yield [node.key!, node.value]; + bucket.push([node.key!, node.value]); } if (node.mid) { - yield* this._entries(node.mid); + this._dfsEntries(node.mid, bucket); } if (node.right) { - yield* this._entries(node.right); + this._dfsEntries(node.right, bucket); } } @@ -1349,47 +1355,3 @@ export class LRUCache extends LinkedMap { } } } - -/** - * Wraps the map in type that only implements readonly properties. Useful - * in the extension host to prevent the consumer from making any mutations. - */ -export class ReadonlyMapView implements ReadonlyMap{ - readonly #source: ReadonlyMap; - - public get size() { - return this.#source.size; - } - - constructor(source: ReadonlyMap) { - this.#source = source; - } - - forEach(callbackfn: (value: V, key: K, map: ReadonlyMap) => void, thisArg?: any): void { - this.#source.forEach(callbackfn, thisArg); - } - - get(key: K): V | undefined { - return this.#source.get(key); - } - - has(key: K): boolean { - return this.#source.has(key); - } - - entries(): IterableIterator<[K, V]> { - return this.#source.entries(); - } - - keys(): IterableIterator { - return this.#source.keys(); - } - - values(): IterableIterator { - return this.#source.values(); - } - - [Symbol.iterator](): IterableIterator<[K, V]> { - return this.#source.entries(); - } -} diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 5c0b6d6054a..288a8daf33b 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -5,14 +5,14 @@ import { extname } from 'vs/base/common/path'; -export namespace Mimes { - export const text = 'text/plain'; - export const binary = 'application/octet-stream'; - export const unknown = 'application/unknown'; - export const markdown = 'text/markdown'; - export const latex = 'text/latex'; - export const uriList = 'text/uri-list'; -} +export const Mimes = Object.freeze({ + text: 'text/plain', + binary: 'application/octet-stream', + unknown: 'application/unknown', + markdown: 'text/markdown', + latex: 'text/latex', + uriList: 'text/uri-list', +}); interface MapExtToMediaMimes { [index: string]: string; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 63d6a15b49e..20f6b38e629 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -101,6 +101,11 @@ export namespace Schemas { * Scheme used vs live share */ export const vsls = 'vsls'; + + /** + * Scheme used for the Source Control commit input's text document + */ + export const vscodeSourceControl = 'vscode-scm'; } export const connectionTokenCookieName = 'vscode-tkn'; diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 62e4aeac226..9e59345c09c 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isArray, isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; export function deepClone(obj: T): T { if (!obj || typeof obj !== 'object') { @@ -61,7 +61,7 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set): return changed; } - if (isArray(obj)) { + if (Array.isArray(obj)) { const r1: any[] = []; for (const e of obj) { r1.push(_cloneAndChange(e, changer, seen)); @@ -186,11 +186,6 @@ export function safeStringify(obj: any): string { }); } -export function getOrDefault(obj: T, fn: (obj: T) => R | undefined, defaultValue: R): R { - const result = fn(obj); - return typeof result === 'undefined' ? defaultValue : result; -} - type obj = { [key: string]: any }; /** * Returns an object that has keys for each value that is different in the base object. Keys @@ -237,3 +232,38 @@ export function filter(obj: obj, predicate: (key: string, value: any) => boolean } return result; } + +export function getAllPropertyNames(obj: object): string[] { + let res: string[] = []; + let proto = Object.getPrototypeOf(obj); + while (Object.prototype !== proto) { + res = res.concat(Object.getOwnPropertyNames(proto)); + proto = Object.getPrototypeOf(proto); + } + return res; +} + +export function getAllMethodNames(obj: object): string[] { + const methods: string[] = []; + for (const prop of getAllPropertyNames(obj)) { + if (typeof (obj as any)[prop] === 'function') { + methods.push(prop); + } + } + return methods; +} + +export function createProxyObject(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T { + const createProxyMethod = (method: string): () => unknown => { + return function () { + const args = Array.prototype.slice.call(arguments, 0); + return invoke(method, args); + }; + }; + + const result = {} as T; + for (const methodName of methodNames) { + (result)[methodName] = createProxyMethod(methodName); + } + return result; +} diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts new file mode 100644 index 00000000000..c588d68b9f6 --- /dev/null +++ b/src/vs/base/common/observable.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export { + IObservable, + IObserver, + IReader, + ISettable, + ISettableObservable, + ITransaction, + observableValue, + transaction, +} from 'vs/base/common/observableImpl/base'; +export { derived } from 'vs/base/common/observableImpl/derived'; +export { + autorun, + autorunDelta, + autorunHandleChanges, + autorunWithStore, +} from 'vs/base/common/observableImpl/autorun'; +export * from 'vs/base/common/observableImpl/utils'; + +import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableImpl/logging'; + +const enableLogging = false; +if (enableLogging) { + setLogger(new ConsoleObservableLogger()); +} diff --git a/src/vs/base/common/observableImpl/autorun.ts b/src/vs/base/common/observableImpl/autorun.ts new file mode 100644 index 00000000000..6efe4736783 --- /dev/null +++ b/src/vs/base/common/observableImpl/autorun.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver(debugName, fn, undefined); +} + +interface IChangeContext { + readonly changedObservable: IObservable; + readonly change: unknown; + + didChange(observable: IObservable): this is { change: TChange }; +} + +export function autorunHandleChanges( + debugName: string, + options: { + /** + * Returns if this change should cause a re-run of the autorun. + */ + handleChange: (context: IChangeContext) => boolean; + }, + fn: (reader: IReader) => void +): IDisposable { + return new AutorunObserver(debugName, fn, options.handleChange); +} + +export function autorunWithStore( + fn: (reader: IReader, store: DisposableStore) => void, + debugName: string +): IDisposable { + const store = new DisposableStore(); + const disposable = autorun( + debugName, + reader => { + store.clear(); + fn(reader, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +export class AutorunObserver implements IObserver, IReader, IDisposable { + public needsToRun = true; + private updateCount = 0; + private disposed = false; + + /** + * The actual dependencies. + */ + private _dependencies = new Set>(); + public get dependencies() { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + constructor( + public readonly debugName: string, + private readonly runFn: (reader: IReader) => void, + private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined + ) { + getLogger()?.handleAutorunCreated(this); + this.runIfNeeded(); + } + + public subscribeTo(observable: IObservable) { + // In case the run action disposes the autorun + if (this.disposed) { + return; + } + this._dependencies.add(observable); + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + public handleChange(observable: IObservable, change: TChange): void { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: o => o === observable as any, + }) : true; + this.needsToRun = this.needsToRun || shouldReact; + + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + public beginUpdate(): void { + this.updateCount++; + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + this.runIfNeeded(); + } + } + + private runIfNeeded(): void { + if (!this.needsToRun) { + return; + } + // Assert: this.staleDependencies is an empty set. + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + this.needsToRun = false; + + getLogger()?.handleAutorunTriggered(this); + + try { + this.runFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + } + + public dispose(): void { + this.disposed = true; + for (const o of this._dependencies) { + o.removeObserver(this); + } + this._dependencies.clear(); + } + + public toString(): string { + return `Autorun<${this.debugName}>`; + } +} + +export namespace autorun { + export const Observer = AutorunObserver; +} +export function autorunDelta( + name: string, + observable: IObservable, + handler: (args: { lastValue: T | undefined; newValue: T }) => void +): IDisposable { + let _lastValue: T | undefined; + return autorun(name, (reader) => { + const newValue = observable.read(reader); + const lastValue = _lastValue; + _lastValue = newValue; + handler({ lastValue, newValue }); + }); +} diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts new file mode 100644 index 00000000000..9d83083e49b --- /dev/null +++ b/src/vs/base/common/observableImpl/base.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { derived } from 'vs/base/common/observableImpl/derived'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export interface IObservable { + readonly TChange: TChange; + + /** + * Reads the current value. + * + * Must not be called from {@link IObserver.handleChange}. + */ + get(): T; + + /** + * Adds an observer. + */ + addObserver(observer: IObserver): void; + removeObserver(observer: IObserver): void; + + /** + * Subscribes the reader to this observable and returns the current value of this observable. + */ + read(reader: IReader): T; + + map(fn: (value: T) => TNew): IObservable; + + readonly debugName: string; +} + +export interface IReader { + /** + * Reports an observable that was read. + * + * Is called by {@link IObservable.read}. + */ + subscribeTo(observable: IObservable): void; +} + +export interface IObserver { + /** + * Indicates that an update operation is about to begin. + * + * During an update, invariants might not hold for subscribed observables and + * change events might be delayed. + * However, all changes must be reported before all update operations are over. + */ + beginUpdate(observable: IObservable): void; + + /** + * Is called by a subscribed observable immediately after it notices a change. + * + * When {@link IObservable.get} returns and no change has been reported, + * there has been no change for that observable. + * + * Implementations must not call into other observables! + * The change should be processed when {@link IObserver.endUpdate} is called. + */ + handleChange(observable: IObservable, change: TChange): void; + + /** + * Indicates that an update operation has completed. + */ + endUpdate(observable: IObservable): void; +} + +export interface ISettable { + set(value: T, transaction: ITransaction | undefined, change: TChange): void; +} + +export interface ITransaction { + /** + * Calls `Observer.beginUpdate` immediately + * and `Observer.endUpdate` when the transaction is complete. + */ + updateObserver( + observer: IObserver, + observable: IObservable + ): void; +} + +let _derived: typeof derived; +/** + * @internal + * This is to allow splitting files. +*/ +export function _setDerived(derived: typeof _derived) { + _derived = derived; +} + +export abstract class ConvenientObservable implements IObservable { + get TChange(): TChange { return null!; } + + public abstract get(): T; + public abstract addObserver(observer: IObserver): void; + public abstract removeObserver(observer: IObserver): void; + + /** @sealed */ + public read(reader: IReader): T { + reader.subscribeTo(this); + return this.get(); + } + + /** @sealed */ + public map(fn: (value: T) => TNew): IObservable { + return _derived( + () => { + const name = getFunctionName(fn); + return name !== undefined ? name : `${this.debugName} (mapped)`; + }, + (reader) => fn(this.read(reader)) + ); + } + + public abstract get debugName(): string; +} + +export abstract class BaseObservable extends ConvenientObservable { + protected readonly observers = new Set(); + + /** @sealed */ + public addObserver(observer: IObserver): void { + const len = this.observers.size; + this.observers.add(observer); + if (len === 0) { + this.onFirstObserverAdded(); + } + } + + /** @sealed */ + public removeObserver(observer: IObserver): void { + const deleted = this.observers.delete(observer); + if (deleted && this.observers.size === 0) { + this.onLastObserverRemoved(); + } + } + + protected onFirstObserverAdded(): void { } + protected onLastObserverRemoved(): void { } +} + +export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + const tx = new TransactionImpl(fn, getDebugName); + try { + getLogger()?.handleBeginTransaction(tx); + fn(tx); + } finally { + tx.finish(); + getLogger()?.handleEndTransaction(); + } +} + +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} + +export class TransactionImpl implements ITransaction { + private updatingObservers: { observer: IObserver; observable: IObservable }[] | null = []; + + constructor(private readonly fn: Function, private readonly _getDebugName?: () => string) { } + + public getDebugName(): string | undefined { + if (this._getDebugName) { + return this._getDebugName(); + } + return getFunctionName(this.fn); + } + + public updateObserver( + observer: IObserver, + observable: IObservable + ): void { + this.updatingObservers!.push({ observer, observable }); + observer.beginUpdate(observable); + } + + public finish(): void { + const updatingObservers = this.updatingObservers!; + // Prevent anyone from updating observers from now on. + this.updatingObservers = null; + for (const { observer, observable } of updatingObservers) { + observer.endUpdate(observable); + } + } +} + +export interface ISettableObservable extends IObservable, ISettable { +} + +export function observableValue(name: string, initialValue: T): ISettableObservable { + return new ObservableValue(name, initialValue); +} + +export class ObservableValue + extends BaseObservable + implements ISettableObservable +{ + private value: T; + + constructor(public readonly debugName: string, initialValue: T) { + super(); + this.value = initialValue; + } + + public get(): T { + return this.value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (this.value === value) { + return; + } + + if (!tx) { + transaction((tx) => { + this.set(value, tx, change); + }, () => `Setting ${this.debugName}`); + return; + } + + const oldValue = this.value; + this.value = value; + getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true }); + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } + + override toString(): string { + return `${this.debugName}: ${this.value}`; + } +} + diff --git a/src/vs/base/common/observableImpl/derived.ts b/src/vs/base/common/observableImpl/derived.ts new file mode 100644 index 00000000000..84a93132f15 --- /dev/null +++ b/src/vs/base/common/observableImpl/derived.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function derived(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable { + return new Derived(debugName, computeFn); +} + +_setDerived(derived); + +export class Derived extends BaseObservable implements IReader, IObserver { + private hadValue = false; + private hasValue = false; + private value: T | undefined = undefined; + private updateCount = 0; + + private _dependencies = new Set>(); + public get dependencies(): ReadonlySet> { + return this._dependencies; + } + + /** + * Dependencies that have to be removed when {@link runFn} ran through. + */ + private staleDependencies = new Set>(); + + public override get debugName(): string { + return typeof this._debugName === 'function' ? this._debugName() : this._debugName; + } + + constructor( + private readonly _debugName: string | (() => string), + private readonly computeFn: (reader: IReader) => T + ) { + super(); + + getLogger()?.handleDerivedCreated(this); + } + + protected override onLastObserverRemoved(): void { + /** + * We are not tracking changes anymore, thus we have to assume + * that our cache is invalid. + */ + this.hasValue = false; + this.hadValue = false; + this.value = undefined; + for (const d of this._dependencies) { + d.removeObserver(this); + } + this._dependencies.clear(); + } + + public get(): T { + if (this.observers.size === 0) { + // Cache is not valid and don't refresh the cache. + // Observables should not be read in non-reactive contexts. + const result = this.computeFn(this); + // Clear new dependencies + this.onLastObserverRemoved(); + return result; + } + + if (this.updateCount > 0 && this.hasValue) { + // Refresh dependencies + for (const d of this._dependencies) { + // Maybe `.get()` triggers `handleChange`? + d.get(); + if (!this.hasValue) { + // The other dependencies will refresh on demand + break; + } + } + } + + if (!this.hasValue) { + const emptySet = this.staleDependencies; + this.staleDependencies = this._dependencies; + this._dependencies = emptySet; + + const oldValue = this.value; + try { + this.value = this.computeFn(this); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.staleDependencies) { + o.removeObserver(this); + } + this.staleDependencies.clear(); + } + + this.hasValue = true; + const didChange = this.hadValue && oldValue !== this.value; + getLogger()?.handleDerivedRecomputed(this, { + oldValue, + newValue: this.value, + change: undefined, + didChange + }); + if (didChange) { + for (const r of this.observers) { + r.handleChange(this, undefined); + } + } + } + return this.value!; + } + + // IObserver Implementation + public beginUpdate(): void { + if (this.updateCount === 0) { + for (const r of this.observers) { + r.beginUpdate(this); + } + } + this.updateCount++; + } + + public handleChange( + _observable: IObservable, + _change: TChange + ): void { + if (this.hasValue) { + this.hadValue = true; + this.hasValue = false; + } + + // Not in transaction: Recompute & inform observers immediately + if (this.updateCount === 0 && this.observers.size > 0) { + this.get(); + } + + // Otherwise, recompute in `endUpdate` or on demand. + } + + public endUpdate(): void { + this.updateCount--; + if (this.updateCount === 0) { + if (this.observers.size > 0) { + // Propagate invalidation + this.get(); + } + + for (const r of this.observers) { + r.endUpdate(this); + } + } + } + + // IReader Implementation + public subscribeTo(observable: IObservable) { + this._dependencies.add(observable); + // We are already added as observer for stale dependencies. + if (!this.staleDependencies.delete(observable)) { + observable.addObserver(this); + } + } + + override toString(): string { + return `LazyDerived<${this.debugName}>`; + } +} diff --git a/src/vs/base/common/observableImpl/logging.ts b/src/vs/base/common/observableImpl/logging.ts new file mode 100644 index 00000000000..0c221d0c700 --- /dev/null +++ b/src/vs/base/common/observableImpl/logging.ts @@ -0,0 +1,312 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AutorunObserver } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, ObservableValue, TransactionImpl } from 'vs/base/common/observableImpl/base'; +import { Derived } from 'vs/base/common/observableImpl/derived'; +import { FromEventObservable } from 'vs/base/common/observableImpl/utils'; + +let globalObservableLogger: IObservableLogger | undefined; + +export function setLogger(logger: IObservableLogger): void { + globalObservableLogger = logger; +} + +export function getLogger(): IObservableLogger | undefined { + return globalObservableLogger; +} + +interface IChangeInformation { + oldValue: unknown; + newValue: unknown; + change: unknown; + didChange: boolean; +} + +export interface IObservableLogger { + handleObservableChanged(observable: ObservableValue, info: IChangeInformation): void; + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; + + handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunTriggered(autorun: AutorunObserver): void; + + handleDerivedCreated(observable: Derived): void; + handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; + + handleBeginTransaction(transaction: TransactionImpl): void; + handleEndTransaction(): void; +} + +export class ConsoleObservableLogger implements IObservableLogger { + private indentation = 0; + + private textToConsoleArgs(text: ConsoleText): unknown[] { + return consoleTextToArgs([ + normalText(repeat('| ', this.indentation)), + text, + ]); + } + + private formatInfo(info: IChangeInformation): ConsoleText[] { + return info.didChange + ? [ + normalText(` `), + styled(formatValue(info.oldValue, 70), { + color: 'red', + strikeThrough: true, + }), + normalText(` `), + styled(formatValue(info.newValue, 60), { + color: 'green', + }), + ] + : [normalText(` (unchanged)`)]; + } + + handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable value changed'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + private readonly changedObservablesSets = new WeakMap>>(); + + formatChanges(changes: Set>): ConsoleText | undefined { + if (changes.size === 0) { + return undefined; + } + return styled( + ' (changed deps: ' + + [...changes].map((o) => o.debugName).join(', ') + + ')', + { color: 'gray' } + ); + } + + handleDerivedCreated(derived: Derived): void { + const existingHandleChange = derived.handleChange; + this.changedObservablesSets.set(derived, new Set()); + derived.handleChange = (observable, change) => { + this.changedObservablesSets.get(derived)!.add(observable); + return existingHandleChange.apply(derived, [observable, change]); + }; + } + + handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { + const changedObservables = this.changedObservablesSets.get(derived)!; + console.log(...this.textToConsoleArgs([ + formatKind('derived recomputed'), + styled(derived.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void { + console.log(...this.textToConsoleArgs([ + formatKind('observable from event triggered'), + styled(observable.debugName, { color: 'BlueViolet' }), + ...this.formatInfo(info), + ])); + } + + handleAutorunCreated(autorun: AutorunObserver): void { + const existingHandleChange = autorun.handleChange; + this.changedObservablesSets.set(autorun, new Set()); + autorun.handleChange = (observable, change) => { + this.changedObservablesSets.get(autorun)!.add(observable); + return existingHandleChange.apply(autorun, [observable, change]); + }; + } + + handleAutorunTriggered(autorun: AutorunObserver): void { + const changedObservables = this.changedObservablesSets.get(autorun)!; + console.log(...this.textToConsoleArgs([ + formatKind('autorun'), + styled(autorun.debugName, { color: 'BlueViolet' }), + this.formatChanges(changedObservables) + ])); + changedObservables.clear(); + } + + handleBeginTransaction(transaction: TransactionImpl): void { + let transactionName = transaction.getDebugName(); + if (transactionName === undefined) { + transactionName = ''; + } + console.log(...this.textToConsoleArgs([ + formatKind('transaction'), + styled(transactionName, { color: 'BlueViolet' }), + ])); + this.indentation++; + } + + handleEndTransaction(): void { + this.indentation--; + } +} + +type ConsoleText = + | (ConsoleText | undefined)[] + | { text: string; style: string; data?: Record } + | { data: Record }; + +function consoleTextToArgs(text: ConsoleText): unknown[] { + const styles = new Array(); + const initial = {}; + const data = initial; + let firstArg = ''; + + function process(t: ConsoleText): void { + if ('length' in t) { + for (const item of t) { + if (item) { + process(item); + } + } + } else if ('text' in t) { + firstArg += `%c${t.text}`; + styles.push(t.style); + if (t.data) { + Object.assign(data, t.data); + } + } else if ('data' in t) { + Object.assign(data, t.data); + } + } + + process(text); + + const result = [firstArg, ...styles]; + if (Object.keys(data).length > 0) { + result.push(data); + } + + return result; +} + +function normalText(text: string): ConsoleText { + return styled(text, { color: 'black' }); +} + +function formatKind(kind: string): ConsoleText { + return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true }); +} + +function styled( + text: string, + options: { color: string; strikeThrough?: boolean; bold?: boolean } = { + color: 'black', + } +): ConsoleText { + function objToCss(styleObj: Record): string { + return Object.entries(styleObj).reduce( + (styleString, [propName, propValue]) => { + return `${styleString}${propName}:${propValue};`; + }, + '' + ); + } + + const style: Record = { + color: options.color, + }; + if (options.strikeThrough) { + style['text-decoration'] = 'line-through'; + } + if (options.bold) { + style['font-weight'] = 'bold'; + } + + return { + text, + style: objToCss(style), + }; +} + +function formatValue(value: unknown, availableLen: number): string { + switch (typeof value) { + case 'number': + return '' + value; + case 'string': + if (value.length + 2 <= availableLen) { + return `"${value}"`; + } + return `"${value.substr(0, availableLen - 7)}"+...`; + + case 'boolean': + return value ? 'true' : 'false'; + case 'undefined': + return 'undefined'; + case 'object': + if (value === null) { + return 'null'; + } + if (Array.isArray(value)) { + return formatArray(value, availableLen); + } + return formatObject(value, availableLen); + case 'symbol': + return value.toString(); + case 'function': + return `[[Function${value.name ? ' ' + value.name : ''}]]`; + default: + return '' + value; + } +} + +function formatArray(value: unknown[], availableLen: number): string { + let result = '[ '; + let first = true; + for (const val of value) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${formatValue(val, availableLen - result.length)}`; + } + result += ' ]'; + return result; +} + +function formatObject(value: object, availableLen: number): string { + let result = '{ '; + let first = true; + for (const [key, val] of Object.entries(value)) { + if (!first) { + result += ', '; + } + if (result.length - 5 > availableLen) { + result += '...'; + break; + } + first = false; + result += `${key}: ${formatValue(val, availableLen - result.length)}`; + } + result += ' }'; + return result; +} + +function repeat(str: string, count: number): string { + let result = ''; + for (let i = 1; i <= count; i++) { + result += str; + } + return result; +} + +function padStr(str: string, length: number): string { + while (str.length < length) { + str += ' '; + } + return str; +} diff --git a/src/vs/base/common/observableImpl/utils.ts b/src/vs/base/common/observableImpl/utils.ts new file mode 100644 index 00000000000..0b07d089b83 --- /dev/null +++ b/src/vs/base/common/observableImpl/utils.ts @@ -0,0 +1,281 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorun } from 'vs/base/common/observableImpl/autorun'; +import { IObservable, BaseObservable, transaction, IReader, ITransaction, ConvenientObservable, IObserver, observableValue, getFunctionName } from 'vs/base/common/observableImpl/base'; +import { derived } from 'vs/base/common/observableImpl/derived'; +import { Event } from 'vs/base/common/event'; +import { getLogger } from 'vs/base/common/observableImpl/logging'; + +export function constObservable(value: T): IObservable { + return new ConstObservable(value); +} + +class ConstObservable extends ConvenientObservable { + constructor(private readonly value: T) { + super(); + } + + public override get debugName(): string { + return this.toString(); + } + + public get(): T { + return this.value; + } + public addObserver(observer: IObserver): void { + // NO OP + } + public removeObserver(observer: IObserver): void { + // NO OP + } + + override toString(): string { + return `Const: ${this.value}`; + } +} + + +export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { + const observable = observableValue<{ value?: T }>('promiseValue', {}); + promise.then((value) => { + observable.set({ value }, undefined); + }); + return observable; +} + +export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; +export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { + return new Promise(resolve => { + const d = autorun('waitForState', reader => { + const currentState = observable.read(reader); + if (predicate(currentState)) { + d.dispose(); + resolve(currentState); + } + }); + }); +} + +export function observableFromEvent( + event: Event, + getValue: (args: TArgs | undefined) => T +): IObservable { + return new FromEventObservable(event, getValue); +} + +export class FromEventObservable extends BaseObservable { + private value: T | undefined; + private hasValue = false; + private subscription: IDisposable | undefined; + + constructor( + private readonly event: Event, + private readonly getValue: (args: TArgs | undefined) => T + ) { + super(); + } + + private getDebugName(): string | undefined { + return getFunctionName(this.getValue); + } + + public get debugName(): string { + const name = this.getDebugName(); + return 'From Event' + (name ? `: ${name}` : ''); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = (args: TArgs | undefined) => { + const newValue = this.getValue(args); + + const didChange = this.value !== newValue; + + getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange }); + + if (didChange) { + this.value = newValue; + + if (this.hasValue) { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => { + const name = this.getDebugName(); + return 'Event fired' + (name ? `: ${name}` : ''); + } + ); + } + this.hasValue = true; + } + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + this.hasValue = false; + this.value = undefined; + } + + public get(): T { + if (this.subscription) { + if (!this.hasValue) { + this.handleEvent(undefined); + } + return this.value!; + } else { + // no cache, as there are no subscribers to keep it updated + return this.getValue(undefined); + } + } +} + +export namespace observableFromEvent { + export const Observer = FromEventObservable; +} + +export function observableSignalFromEvent( + debugName: string, + event: Event +): IObservable { + return new FromEventObservableSignal(debugName, event); +} + +class FromEventObservableSignal extends BaseObservable { + private subscription: IDisposable | undefined; + + constructor( + public readonly debugName: string, + private readonly event: Event, + ) { + super(); + } + + protected override onFirstObserverAdded(): void { + this.subscription = this.event(this.handleEvent); + } + + private readonly handleEvent = () => { + transaction( + (tx) => { + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, undefined); + } + }, + () => this.debugName + ); + }; + + protected override onLastObserverRemoved(): void { + this.subscription!.dispose(); + this.subscription = undefined; + } + + public override get(): void { + // NO OP + } +} + +export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { + const debouncedObservable = observableValue('debounced', undefined); + + let timeout: any = undefined; + + disposableStore.add(autorun('debounce', reader => { + const value = observable.read(reader); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + transaction(tx => { + debouncedObservable.set(value, tx); + }); + }, debounceMs); + + })); + + return debouncedObservable; +} + +export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { + const observable = observableValue('triggeredRecently', false); + + let timeout: any = undefined; + + disposableStore.add(event(() => { + observable.set(true, undefined); + + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + observable.set(false, undefined); + }, timeoutMs); + })); + + return observable; +} + +/** + * This ensures the observable is kept up-to-date. + * This is useful when the observables `get` method is used. +*/ +export function keepAlive(observable: IObservable): IDisposable { + const o = new KeepAliveObserver(); + observable.addObserver(o); + return toDisposable(() => { + observable.removeObserver(o); + }); +} + +class KeepAliveObserver implements IObserver { + beginUpdate(observable: IObservable): void { + // NO OP + } + + handleChange(observable: IObservable, change: TChange): void { + // NO OP + } + + endUpdate(observable: IObservable): void { + // NO OP + } +} + +export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { + let lastValue: T | undefined = undefined; + const observable = derived(name, reader => { + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return observable; +} + +export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { + let lastValue: T | undefined = undefined; + const counter = observableValue('derivedObservableWithWritableCache.counter', 0); + const observable = derived(name, reader => { + counter.read(reader); + lastValue = computeFn(reader, lastValue); + return lastValue; + }); + return Object.assign(observable, { + clearCache: (transaction: ITransaction) => { + lastValue = undefined; + counter.set(counter.get() + 1, transaction); + }, + }); +} diff --git a/src/vs/base/common/observableValue.ts b/src/vs/base/common/observableValue.ts index 7bdcbae66cb..e1f207c0841 100644 --- a/src/vs/base/common/observableValue.ts +++ b/src/vs/base/common/observableValue.ts @@ -5,17 +5,28 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +//@ts-ignore +import type { IObservable } from 'vs/base/common/observable'; +/** + * @deprecated Use {@link IObservable} instead. + */ export interface IObservableValue { onDidChange: Event; readonly value: T; } +/** + * @deprecated Use {@link IObservable} instead. + */ export const staticObservableValue = (value: T): IObservableValue => ({ onDidChange: Event.None, value, }); +/** + * @deprecated Use {@link IObservable} instead. + */ export class MutableObservableValue extends Disposable implements IObservableValue { private readonly changeEmitter = this._register(new Emitter()); diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts index 60f72b407c3..1d9b7938169 100644 --- a/src/vs/base/common/paging.ts +++ b/src/vs/base/common/paging.ts @@ -6,7 +6,6 @@ import { range } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; -import { isArray } from 'vs/base/common/types'; /** * A Pager is a stateless abstraction over a paged collection. @@ -65,7 +64,7 @@ export class PagedModel implements IPagedModel { get length(): number { return this.pager.total; } constructor(arg: IPager | T[]) { - this.pager = isArray(arg) ? singlePagePager(arg) : arg; + this.pager = Array.isArray(arg) ? singlePagePager(arg) : arg; const totalPages = Math.ceil(this.pager.total / this.pager.pageSize); diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 9a54c0556ff..5fd52a6fa76 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; const LANGUAGE_DEFAULT = 'en'; @@ -67,7 +68,6 @@ const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer' interface INavigator { userAgent: string; - language: string; maxTouchPoints?: number; } declare const navigator: INavigator; @@ -81,17 +81,15 @@ if (typeof navigator === 'object' && !isElectronRenderer) { _isLinux = _userAgent.indexOf('Linux') >= 0; _isWeb = true; - // Gather loader configuration since that contains the locale - let loaderConfiguration: any = null; - if (typeof globals.require !== 'undefined' && typeof globals.require.getConfig === 'function') { - // Get the configuration from the Monaco AMD Loader - loaderConfiguration = globals.require.getConfig(); - } else if (typeof globals.requirejs !== 'undefined') { - // Get the configuration from requirejs - loaderConfiguration = globals.requirejs.s.contexts._.config; - } - const configuredLocale = loaderConfiguration?.['vs/nls']?.['availableLanguages']?.['*'] as string | undefined; - _locale = configuredLocale || navigator.language; + const configuredLocale = nls.getConfiguredDefaultLocale( + // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale` + // to ensure that the NLS AMD Loader plugin has been loaded and configured. + // This is because the loader plugin decides what the default locale is based on + // how it's able to resolve the strings. + nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') + ); + + _locale = configuredLocale || LANGUAGE_DEFAULT; _language = _locale; } diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 568ef068648..1ae8079810e 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -99,7 +99,6 @@ export interface IProductConfiguration { readonly enableTelemetry?: boolean; readonly openToWelcomeMainPage?: boolean; readonly aiConfig?: { - readonly asimovKey: string; readonly ariaKey: string; }; @@ -154,7 +153,7 @@ export interface IProductConfiguration { readonly 'configurationSync.store'?: ConfigurationSyncStore; - readonly 'sessionSync.store'?: Omit; + readonly 'editSessions.store'?: Omit; readonly darwinUniversalAssetId?: string; } diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index 7da28a8555d..89c832edcd5 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -268,9 +268,7 @@ export class Scrollable extends Disposable { this._setState(newState, Boolean(this._smoothScrolling)); // Validate outstanding animated scroll position target - if (this._smoothScrolling) { - this._smoothScrolling.acceptScrollDimensions(this._state); - } + this._smoothScrolling?.acceptScrollDimensions(this._state); } /** diff --git a/src/vs/base/common/sequence.ts b/src/vs/base/common/sequence.ts index ac3593d8990..2559c069be8 100644 --- a/src/vs/base/common/sequence.ts +++ b/src/vs/base/common/sequence.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; export interface ISplice { readonly start: number; @@ -33,26 +32,3 @@ export class Sequence implements ISequence, ISpliceable { this._onDidSplice.fire({ start, deleteCount, toInsert }); } } - -export class SimpleSequence implements ISequence { - - private _elements: T[]; - get elements(): T[] { return this._elements; } - - readonly onDidSplice: Event>; - private disposable: IDisposable; - - constructor(elements: T[], onDidAdd: Event, onDidRemove: Event) { - this._elements = [...elements]; - this.onDidSplice = Event.any( - Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })), - Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] })) - ); - - this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert)); - } - - dispose(): void { - this.disposable.dispose(); - } -} diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 28425d89a40..53796c58d8b 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -378,6 +378,10 @@ export function compareSubstringIgnoreCase(a: string, b: string, aStart: number return 0; } +export function isAsciiDigit(code: number): boolean { + return code >= CharCode.Digit0 && code <= CharCode.Digit9; +} + export function isLowerAsciiLetter(code: number): boolean { return code >= CharCode.a && code <= CharCode.z; } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 50ac38cf407..08063ae38d1 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,15 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; - -/** - * @returns whether the provided parameter is a JavaScript Array or not. - */ -export function isArray(array: any): array is any[] { - return Array.isArray(array); -} - /** * @returns whether the provided parameter is a JavaScript String or not. */ @@ -27,7 +18,6 @@ export function isStringArray(value: unknown): value is string[] { } /** - * * @returns whether the provided parameter is of type `object` but **not** * `null`, an `array`, a `regexp`, nor a `date`. */ @@ -43,22 +33,12 @@ export function isObject(obj: unknown): obj is Object { } /** - * * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type */ export function isTypedArray(obj: unknown): obj is Object { + const TypedArray = Object.getPrototypeOf(Uint8Array); return typeof obj === 'object' - && (obj instanceof Uint8Array || - obj instanceof Uint16Array || - obj instanceof Uint32Array || - obj instanceof Float32Array || - obj instanceof Float64Array || - obj instanceof Int8Array || - obj instanceof Int16Array || - obj instanceof Int32Array || - obj instanceof BigInt64Array || - obj instanceof BigUint64Array || - obj instanceof Uint8ClampedArray); + && obj instanceof TypedArray; } /** @@ -210,41 +190,6 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un } } -export function getAllPropertyNames(obj: object): string[] { - let res: string[] = []; - let proto = Object.getPrototypeOf(obj); - while (Object.prototype !== proto) { - res = res.concat(Object.getOwnPropertyNames(proto)); - proto = Object.getPrototypeOf(proto); - } - return res; -} - -export function getAllMethodNames(obj: object): string[] { - const methods: string[] = []; - for (const prop of getAllPropertyNames(obj)) { - if (typeof (obj as any)[prop] === 'function') { - methods.push(prop); - } - } - return methods; -} - -export function createProxyObject(methodNames: string[], invoke: (method: string, args: unknown[]) => unknown): T { - const createProxyMethod = (method: string): () => unknown => { - return function () { - const args = Array.prototype.slice.call(arguments, 0); - return invoke(method, args); - }; - }; - - const result = {} as T; - for (const methodName of methodNames) { - (result)[methodName] = createProxyMethod(methodName); - } - return result; -} - /** * Converts null to undefined, passes all other values through. */ @@ -275,12 +220,7 @@ export type AddFirstParameterToFunctions; */ -export type UriDto = { [K in keyof T]: T[K] extends URI - ? UriComponents - : UriDto }; - -export function assertNever(value: never, message = 'Unreachable'): never { - throw new Error(message); -} +export type AtLeastOne }> = Partial & U[keyof U]; diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 4aa3825cb4e..744afa00f0c 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -701,3 +701,10 @@ function percentDecode(str: string): string { } return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); } + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents + */ +export type UriDto = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto }; diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 5e9df9db7dd..17ecac8ff27 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -6,8 +6,8 @@ import { transformErrorForSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { getAllMethodNames } from 'vs/base/common/objects'; import { globals, isWeb } from 'vs/base/common/platform'; -import * as types from 'vs/base/common/types'; import * as strings from 'vs/base/common/strings'; const INITIALIZE = '$initialize'; @@ -332,7 +332,7 @@ export class SimpleWorkerClient extends Disp loaderConfiguration = globals.requirejs.s.contexts._.config; } - const hostMethods = types.getAllMethodNames(host); + const hostMethods = getAllMethodNames(host); // Send initialize message this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [ @@ -507,7 +507,7 @@ export class SimpleWorkerServer { if (this._requestHandlerFactory) { // static request handler this._requestHandler = this._requestHandlerFactory(hostProxy); - return Promise.resolve(types.getAllMethodNames(this._requestHandler)); + return Promise.resolve(getAllMethodNames(this._requestHandler)); } if (loaderConfig) { @@ -548,7 +548,7 @@ export class SimpleWorkerServer { return; } - resolve(types.getAllMethodNames(this._requestHandler)); + resolve(getAllMethodNames(this._requestHandler)); }, reject); }); } diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts deleted file mode 100644 index 36a3de5175c..00000000000 --- a/src/vs/base/node/decoder.ts +++ /dev/null @@ -1,62 +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 sd from 'string_decoder'; -import { CharCode } from 'vs/base/common/charCode'; - -/** - * Convenient way to iterate over output line by line. This helper accommodates for the fact that - * a buffer might not end with new lines all the way. - * - * To use: - * - call the write method - * - forEach() over the result to get the lines - */ -export class LineDecoder { - private stringDecoder: sd.StringDecoder; - private remaining: string | null; - - constructor(encoding: BufferEncoding = 'utf8') { - this.stringDecoder = new sd.StringDecoder(encoding); - this.remaining = null; - } - - write(buffer: Buffer): string[] { - const result: string[] = []; - const value = this.remaining - ? this.remaining + this.stringDecoder.write(buffer) - : this.stringDecoder.write(buffer); - - if (value.length < 1) { - return result; - } - let start = 0; - let ch: number; - let idx = start; - while (idx < value.length) { - ch = value.charCodeAt(idx); - if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) { - result.push(value.substring(start, idx)); - idx++; - if (idx < value.length) { - const lastChar = ch; - ch = value.charCodeAt(idx); - if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) { - idx++; - } - } - start = idx; - } else { - idx++; - } - } - this.remaining = start < value.length ? value.substr(start) : null; - return result; - } - - end(): string | null { - return this.remaining; - } -} diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 2283c4a61d6..9b652eb627f 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -390,6 +390,9 @@ interface IEnsuredWriteFileOptions extends IWriteFileOptions { } let canFlush = true; +export function configureFlushOnWrite(enabled: boolean): void { + canFlush = enabled; +} // Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk // We do this in cases where we want to make sure the data is really on disk and @@ -421,7 +424,7 @@ function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, o // In that case we disable flushing and warn to the console if (syncError) { console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError); - canFlush = false; + configureFlushOnWrite(false); } return fs.close(fd, closeError => callback(closeError)); @@ -455,7 +458,7 @@ export function writeFileSync(path: string, data: string | Buffer, options?: IWr fs.fdatasyncSync(fd); // https://github.com/microsoft/vscode/issues/9589 } catch (syncError) { console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError); - canFlush = false; + configureFlushOnWrite(false); } } finally { fs.closeSync(fd); diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 1a27f7543c3..a71a7b8cb4e 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -5,392 +5,23 @@ import * as cp from 'child_process'; import { Stats } from 'fs'; -import { IStringDictionary } from 'vs/base/common/collections'; -import * as extpath from 'vs/base/common/extpath'; -import { FileAccess } from 'vs/base/common/network'; -import * as Objects from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; import * as Platform from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; -import { CommandOptions, Executable, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from 'vs/base/common/processes'; +import { CommandOptions, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from 'vs/base/common/processes'; import * as Types from 'vs/base/common/types'; -import { LineDecoder } from 'vs/base/node/decoder'; import * as pfs from 'vs/base/node/pfs'; -import * as nls from 'vs/nls'; export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode }; export type ValueCallback = (value: T | Promise) => void; export type ErrorCallback = (error?: any) => void; export type ProgressCallback = (progress: T) => void; -export interface LineData { - line: string; - source: Source; -} - -function getWindowsCode(status: number): TerminateResponseCode { - switch (status) { - case 0: - return TerminateResponseCode.Success; - case 1: - return TerminateResponseCode.AccessDenied; - case 128: - return TerminateResponseCode.ProcessNotFound; - default: - return TerminateResponseCode.Unknown; - } -} - -function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise { - if (Platform.isWindows) { - try { - const options: any = { - stdio: ['pipe', 'pipe', 'ignore'] - }; - if (cwd) { - options.cwd = cwd; - } - const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid!.toString()], options); - return new Promise(resolve => { - killProcess.once('error', (err) => { - resolve({ success: false, error: err }); - }); - killProcess.once('exit', (code, signal) => { - if (code === 0) { - resolve({ success: true }); - } else { - resolve({ success: false, code: code !== null ? code : TerminateResponseCode.Unknown }); - } - }); - }); - } catch (err) { - return Promise.resolve({ success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown }); - } - } else if (Platform.isLinux || Platform.isMacintosh) { - try { - const cmd = FileAccess.asFileUri('vs/base/node/terminateProcess.sh', require).fsPath; - return new Promise(resolve => { - cp.execFile(cmd, [process.pid!.toString()], { encoding: 'utf8', shell: true } as cp.ExecFileOptions, (err, stdout, stderr) => { - if (err) { - resolve({ success: false, error: err }); - } else { - resolve({ success: true }); - } - }); - }); - } catch (err) { - return Promise.resolve({ success: false, error: err }); - } - } else { - process.kill('SIGKILL'); - } - return Promise.resolve({ success: true }); -} export function getWindowsShell(env = process.env as Platform.IProcessEnvironment): string { return env['comspec'] || 'cmd.exe'; } -export abstract class AbstractProcess { - private cmd: string; - private args: string[]; - private options: CommandOptions | ForkOptions; - protected shell: boolean; - - private childProcess: cp.ChildProcess | null; - protected childProcessPromise: Promise | null; - private pidResolve: ValueCallback | undefined; - protected terminateRequested: boolean; - - private static WellKnowCommands: IStringDictionary = { - 'ant': true, - 'cmake': true, - 'eslint': true, - 'gradle': true, - 'grunt': true, - 'gulp': true, - 'jake': true, - 'jenkins': true, - 'jshint': true, - 'make': true, - 'maven': true, - 'msbuild': true, - 'msc': true, - 'nmake': true, - 'npm': true, - 'rake': true, - 'tsc': true, - 'xbuild': true - }; - - public constructor(executable: Executable); - public constructor(cmd: string, args: string[] | undefined, shell: boolean, options: CommandOptions | undefined); - public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean, arg4?: CommandOptions) { - if (arg2 !== undefined && arg3 !== undefined && arg4 !== undefined) { - this.cmd = arg1; - this.args = arg2; - this.shell = arg3; - this.options = arg4; - } else { - const executable = arg1; - this.cmd = executable.command; - this.shell = executable.isShellCommand; - this.args = executable.args.slice(0); - this.options = executable.options || {}; - } - - this.childProcess = null; - this.childProcessPromise = null; - this.terminateRequested = false; - - if (this.options.env) { - const newEnv: IStringDictionary = Object.create(null); - Object.keys(process.env).forEach((key) => { - newEnv[key] = process.env[key]!; - }); - Object.keys(this.options.env).forEach((key) => { - newEnv[key] = this.options.env![key]!; - }); - this.options.env = newEnv; - } - } - - public getSanitizedCommand(): string { - let result = this.cmd.toLowerCase(); - const index = result.lastIndexOf(path.sep); - if (index !== -1) { - result = result.substring(index + 1); - } - if (AbstractProcess.WellKnowCommands[result]) { - return result; - } - return 'other'; - } - - public start(pp: ProgressCallback): Promise { - if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) { - return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.'))); - } - return this.useExec().then((useExec) => { - let cc: ValueCallback; - let ee: ErrorCallback; - const result = new Promise((c, e) => { - cc = c; - ee = e; - }); - - if (useExec) { - let cmd: string = this.cmd; - if (this.args) { - cmd = cmd + ' ' + this.args.join(' '); - } - this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => { - this.childProcess = null; - const err: any = error; - // This is tricky since executing a command shell reports error back in case the executed command return an - // error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we - // always parse the output and report success unless the job got killed. - if (err && err.killed) { - ee({ killed: this.terminateRequested, stdout: stdout.toString(), stderr: stderr.toString() }); - } else { - this.handleExec(cc, pp, error, stdout as any, stderr as any); - } - }); - } else { - let childProcess: cp.ChildProcess | null = null; - const closeHandler = (data: any) => { - this.childProcess = null; - this.childProcessPromise = null; - this.handleClose(data, cc, pp, ee); - const result: SuccessData = { - terminated: this.terminateRequested - }; - if (Types.isNumber(data)) { - result.cmdCode = data; - } - cc(result); - }; - if (this.shell && Platform.isWindows) { - const options: any = Objects.deepClone(this.options); - options.windowsVerbatimArguments = true; - options.detached = false; - let quotedCommand: boolean = false; - let quotedArg: boolean = false; - const commandLine: string[] = []; - let quoted = this.ensureQuotes(this.cmd); - commandLine.push(quoted.value); - quotedCommand = quoted.quoted; - if (this.args) { - this.args.forEach((elem) => { - quoted = this.ensureQuotes(elem); - commandLine.push(quoted.value); - quotedArg = quotedArg && quoted.quoted; - }); - } - const args: string[] = [ - '/s', - '/c', - ]; - if (quotedCommand) { - if (quotedArg) { - args.push('"' + commandLine.join(' ') + '"'); - } else if (commandLine.length > 1) { - args.push('"' + commandLine[0] + '"' + ' ' + commandLine.slice(1).join(' ')); - } else { - args.push('"' + commandLine[0] + '"'); - } - } else { - args.push(commandLine.join(' ')); - } - childProcess = cp.spawn(getWindowsShell(), args, options); - } else { - if (this.cmd) { - childProcess = cp.spawn(this.cmd, this.args, this.options); - } - } - if (childProcess) { - this.childProcess = childProcess; - this.childProcessPromise = Promise.resolve(childProcess); - if (this.pidResolve) { - this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1); - this.pidResolve = undefined; - } - childProcess.on('error', (error: Error) => { - this.childProcess = null; - ee({ terminated: this.terminateRequested, error: error }); - }); - if (childProcess.pid) { - this.childProcess.on('close', closeHandler); - this.handleSpawn(childProcess, cc!, pp, ee!, true); - } - } - } - return result; - }); - } - - protected abstract handleExec(cc: ValueCallback, pp: ProgressCallback, error: Error | null, stdout: Buffer, stderr: Buffer): void; - protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback, sync: boolean): void; - - protected handleClose(data: any, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback): void { - // Default is to do nothing. - } - - private static readonly regexp = /^[^"].* .*[^"]/; - private ensureQuotes(value: string) { - if (AbstractProcess.regexp.test(value)) { - return { - value: '"' + value + '"', //`"${value}"`, - quoted: true - }; - } else { - return { - value: value, - quoted: value.length > 0 && value[0] === '"' && value[value.length - 1] === '"' - }; - } - } - - public get pid(): Promise { - if (this.childProcessPromise) { - return this.childProcessPromise.then(childProcess => childProcess.pid!, err => -1); - } else { - return new Promise((resolve) => { - this.pidResolve = resolve; - }); - } - } - - public terminate(): Promise { - if (!this.childProcessPromise) { - return Promise.resolve({ success: true }); - } - return this.childProcessPromise.then((childProcess) => { - this.terminateRequested = true; - return terminateProcess(childProcess, this.options.cwd).then(response => { - if (response.success) { - this.childProcess = null; - } - return response; - }); - }, (err) => { - return { success: true }; - }); - } - - private useExec(): Promise { - return new Promise(resolve => { - if (!this.shell || !Platform.isWindows) { - return resolve(false); - } - const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']); - cmdShell.on('error', (error: Error) => { - return resolve(true); - }); - cmdShell.on('exit', (data: any) => { - return resolve(false); - }); - }); - } -} - -export class LineProcess extends AbstractProcess { - - private stdoutLineDecoder: LineDecoder | null; - private stderrLineDecoder: LineDecoder | null; - - public constructor(executable: Executable); - public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions); - public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) { - super(arg1, arg2, arg3, arg4); - - this.stdoutLineDecoder = null; - this.stderrLineDecoder = null; - } - - protected handleExec(cc: ValueCallback, pp: ProgressCallback, error: Error, stdout: Buffer, stderr: Buffer) { - [stdout, stderr].forEach((buffer: Buffer, index: number) => { - const lineDecoder = new LineDecoder(); - const lines = lineDecoder.write(buffer); - lines.forEach((line) => { - pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); - }); - const line = lineDecoder.end(); - if (line) { - pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); - } - }); - cc({ terminated: this.terminateRequested, error: error }); - } - - protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback, sync: boolean): void { - const stdoutLineDecoder = new LineDecoder(); - const stderrLineDecoder = new LineDecoder(); - childProcess.stdout!.on('data', (data: Buffer) => { - const lines = stdoutLineDecoder.write(data); - lines.forEach(line => pp({ line: line, source: Source.stdout })); - }); - childProcess.stderr!.on('data', (data: Buffer) => { - const lines = stderrLineDecoder.write(data); - lines.forEach(line => pp({ line: line, source: Source.stderr })); - }); - - this.stdoutLineDecoder = stdoutLineDecoder; - this.stderrLineDecoder = stderrLineDecoder; - } - - protected override handleClose(data: any, cc: ValueCallback, pp: ProgressCallback, ee: ErrorCallback): void { - const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null; - if (stdoutLine) { - pp({ line: stdoutLine, source: Source.stdout }); - } - const stderrLine = this.stderrLineDecoder ? this.stderrLineDecoder.end() : null; - if (stderrLine) { - pp({ line: stderrLine, source: Source.stderr }); - } - } -} - export interface IQueuedSender { send: (msg: any) => void; } diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 4eded868127..8498145bed2 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -52,6 +52,7 @@ export function listProcesses(rootPid: number): Promise { const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/; const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/; const UTILITY_NETWORK_HINT = /--utility-sub-type=network/; + const UTILITY_EXTENSION_HOST_HINT = /--utility-sub-type=node.mojom.NodeService/; const WINDOWS_CRASH_REPORTER = /--crashes-directory/; const WINDOWS_PTY = /\\pipe\\winpty-control/; const WINDOWS_CONSOLE_HOST = /conhost\.exe/; @@ -93,6 +94,10 @@ export function listProcesses(rootPid: number): Promise { if (UTILITY_NETWORK_HINT.exec(cmd)) { return 'utility-network-service'; } + + if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) { + return 'extension-host'; + } } return matches[1]; } diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index c815e61861a..2033f7cd98e 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -81,9 +81,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa let istream: WriteStream; token.onCancellationRequested(() => { - if (istream) { - istream.destroy(); - } + istream?.destroy(); }); return Promise.resolve(Promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise((c, e) => { diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5d464d87e87..badadce84f4 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -136,6 +136,12 @@ export interface WebSocketCloseEvent { export type SocketCloseEvent = NodeSocketCloseEvent | WebSocketCloseEvent | undefined; +export interface SocketTimeoutEvent { + readonly unacknowledgedMsgCount: number; + readonly timeSinceOldestUnacknowledgedMsg: number; + readonly timeSinceLastReceivedSomeData: number; +} + export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; onClose(listener: (e: SocketCloseEvent) => void): IDisposable; @@ -667,6 +673,16 @@ class Queue { this._last = null; } + public length(): number { + let result = 0; + let current = this._first; + while (current) { + current = current.next; + result++; + } + return result; + } + public peek(): T | null { if (!this._first) { return null; @@ -800,8 +816,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { private readonly _onSocketClose = new BufferedEmitter(); readonly onSocketClose: Event = this._onSocketClose.event; - private readonly _onSocketTimeout = new BufferedEmitter(); - readonly onSocketTimeout: Event = this._onSocketTimeout.event; + private readonly _onSocketTimeout = new BufferedEmitter(); + readonly onSocketTimeout: Event = this._onSocketTimeout.event; public get unacknowledgedCount(): number { return this._outgoingMsgId - this._outgoingAckId; @@ -1081,7 +1097,11 @@ export class PersistentProtocol implements IMessagePassingProtocol { if (!this._loadEstimator.hasHighLoad()) { // Trash the socket this._lastSocketTimeoutTime = Date.now(); - this._onSocketTimeout.fire(undefined); + this._onSocketTimeout.fire({ + unacknowledgedMsgCount: this._outgoingUnackMsg.length(), + timeSinceOldestUnacknowledgedMsg, + timeSinceLastReceivedSomeData + }); return; } } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 42033ee91aa..3979b73f208 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -8,7 +8,7 @@ import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/com import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; -import * as errors from 'vs/base/common/errors'; +import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event, EventMultiplexer, Relay } from 'vs/base/common/event'; import { combinedDisposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; @@ -516,7 +516,7 @@ export class ChannelClient implements IChannelClient, IDisposable { return { call(command: string, arg?: any, cancellationToken?: CancellationToken) { if (that.isDisposed) { - return Promise.reject(errors.canceled()); + return Promise.reject(new CancellationError()); } return that.requestPromise(channelName, command, arg, cancellationToken); }, @@ -535,14 +535,14 @@ export class ChannelClient implements IChannelClient, IDisposable { const request: IRawRequest = { id, type, channelName, name, arg }; if (cancellationToken.isCancellationRequested) { - return Promise.reject(errors.canceled()); + return Promise.reject(new CancellationError()); } let disposable: IDisposable; const result = new Promise((c, e) => { if (cancellationToken.isCancellationRequested) { - return e(errors.canceled()); + return e(new CancellationError()); } const doRequest = () => { @@ -591,7 +591,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.sendRequest({ id, type: RequestType.PromiseCancel }); } - e(errors.canceled()); + e(new CancellationError()); }; const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel); @@ -640,18 +640,14 @@ export class ChannelClient implements IChannelClient, IDisposable { case RequestType.Promise: case RequestType.EventListen: { const msgLength = this.send([request.type, request.id, request.channelName, request.name], request.arg); - if (this.logger) { - this.logger.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`, request.arg); - } + this.logger?.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`, request.arg); return; } case RequestType.PromiseCancel: case RequestType.EventDispose: { const msgLength = this.send([request.type, request.id]); - if (this.logger) { - this.logger.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, requestTypeToStr(request.type)); - } + this.logger?.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, requestTypeToStr(request.type)); return; } } @@ -682,18 +678,14 @@ export class ChannelClient implements IChannelClient, IDisposable { switch (type) { case ResponseType.Initialize: - if (this.logger) { - this.logger.logIncoming(message.byteLength, 0, RequestInitiator.LocalSide, responseTypeToStr(type)); - } + this.logger?.logIncoming(message.byteLength, 0, RequestInitiator.LocalSide, responseTypeToStr(type)); return this.onResponse({ type: header[0] }); case ResponseType.PromiseSuccess: case ResponseType.PromiseError: case ResponseType.EventFire: case ResponseType.PromiseErrorObj: - if (this.logger) { - this.logger.logIncoming(message.byteLength, header[1], RequestInitiator.LocalSide, responseTypeToStr(type), body); - } + this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.LocalSide, responseTypeToStr(type), body); return this.onResponse({ type: header[0], id: header[1], data: body }); } } diff --git a/src/vs/base/parts/ipc/electron-main/ipc.electron.ts b/src/vs/base/parts/ipc/electron-main/ipc.electron.ts index f0d395c4bfa..b40d381cffb 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.electron.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.electron.ts @@ -37,9 +37,7 @@ export class Server extends IPCServer { const id = webContents.id; const client = Server.Clients.get(id); - if (client) { - client.dispose(); - } + client?.dispose(); const onDidClientReconnect = new Emitter(); Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire())); diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index fa6d8073ab7..90595976349 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -241,9 +241,7 @@ export class Client implements IChannelClient, IDisposable { console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal); } - if (this.disposeDelayer) { - this.disposeDelayer.cancel(); - } + this.disposeDelayer?.cancel(); this.disposeClient(); this._onDidProcessExit.fire({ code, signal }); }); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 9831dcde97c..bff8e41d8e5 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -3,9 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createHash } from 'crypto'; -import { createConnection, createServer, Server as NetServer, Socket } from 'net'; -import { tmpdir } from 'os'; +// import { createHash } from 'crypto'; +import type { Server as NetServer, Socket } from 'net'; +// import { tmpdir } from 'os'; +import type * as zlib from 'zlib'; import { VSBuffer } from 'vs/base/common/buffer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -15,7 +16,16 @@ import { Platform, platform } from 'vs/base/common/platform'; import { generateUuid } from 'vs/base/common/uuid'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; -import * as zlib from 'zlib'; + +// TODO@bpasero remove me once electron utility process has landed +function getNodeDependencies() { + return { + crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'), + zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'), + net: (require.__$__nodeRequire('net') as any) as typeof import('net'), + os: (require.__$__nodeRequire('os') as any) as typeof import('os') + }; +} export class NodeSocket implements ISocket { @@ -580,7 +590,7 @@ class ZlibInflateStream extends Disposable { options: zlib.ZlibOptions ) { super(); - this._zlibInflate = zlib.createInflateRaw(options); + this._zlibInflate = getNodeDependencies().zlib.createInflateRaw(options); this._zlibInflate.on('error', (err) => { this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (err)?.code }); this._onError.fire(err); @@ -631,7 +641,7 @@ class ZlibDeflateStream extends Disposable { ) { super(); - this._zlibDeflate = zlib.createDeflateRaw({ + this._zlibDeflate = getNodeDependencies().zlib.createDeflateRaw({ windowBits: 15 }); this._zlibDeflate.on('error', (err) => { @@ -692,7 +702,8 @@ function unmask(buffer: VSBuffer, mask: number): void { // Read this before there's any chance it is overwritten // Related to https://github.com/microsoft/vscode/issues/30624 -export const XDG_RUNTIME_DIR = process.env['XDG_RUNTIME_DIR']; +// TODO@bpasero revert me once electron utility process has landed +export const XDG_RUNTIME_DIR = typeof process !== 'undefined' ? process.env['XDG_RUNTIME_DIR'] : undefined; const safeIpcPathLengths: { [platform: number]: number } = { [Platform.Linux]: 107, @@ -713,7 +724,7 @@ export function createRandomIPCHandle(): string { if (XDG_RUNTIME_DIR) { result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`); } else { - result = join(tmpdir(), `vscode-ipc-${randomSuffix}.sock`); + result = join(getNodeDependencies().os.tmpdir(), `vscode-ipc-${randomSuffix}.sock`); } // Validate length @@ -723,7 +734,7 @@ export function createRandomIPCHandle(): string { } export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string { - const scope = createHash('md5').update(directoryPath).digest('hex'); + const scope = getNodeDependencies().crypto.createHash('md5').update(directoryPath).digest('hex'); // Windows: use named pipe if (process.platform === 'win32') { @@ -785,7 +796,7 @@ export function serve(port: number): Promise; export function serve(namedPipe: string): Promise; export function serve(hook: any): Promise { return new Promise((c, e) => { - const server = createServer(); + const server = getNodeDependencies().net.createServer(); server.on('error', e); server.listen(hook, () => { @@ -800,7 +811,7 @@ export function connect(port: number, clientId: string): Promise; export function connect(namedPipe: string, clientId: string): Promise; export function connect(hook: any, clientId: string): Promise { return new Promise((c, e) => { - const socket = createConnection(hook, () => { + const socket = getNodeDependencies().net.createConnection(hook, () => { socket.removeListener('error', e); c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); }); diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index efab21e77bf..e3357d75fe8 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -7,7 +7,6 @@ position: absolute; width: 600px; z-index: 2550; - padding: 0 1px 1px 1px; left: 50%; margin-left: -300px; -webkit-app-region: no-drag; @@ -151,6 +150,7 @@ .quick-input-list { line-height: 22px; margin-top: 6px; + padding: 0px 1px 1px 1px; } .quick-input-widget.hidden-input .quick-input-list { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 1a496080028..e2d0526b38b 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -450,6 +450,7 @@ class QuickPick extends QuickInput implements IQuickPi private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; + private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy'; private _sortByLabel = true; private _autoFocusOnList = true; private _keepScrollPosition = false; @@ -595,6 +596,15 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get matchOnLabelMode() { + return this._matchOnLabelMode; + } + + set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') { + this._matchOnLabelMode = matchOnLabelMode; + this.update(); + } + get sortByLabel() { return this._sortByLabel; } @@ -994,6 +1004,7 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.matchOnLabelMode = this.matchOnLabelMode; this.ui.list.sortByLabel = this.sortByLabel; if (this.itemsUpdated) { this.itemsUpdated = false; @@ -1599,9 +1610,7 @@ export class QuickInputController extends Disposable { this.onShowEmitter.fire(); const oldController = this.controller; this.controller = controller; - if (oldController) { - oldController.didHide(); - } + oldController?.didHide(); this.setEnabled(true); ui.leftActionBar.clear(); @@ -1675,10 +1684,10 @@ export class QuickInputController extends Disposable { if (enabled !== this.enabled) { this.enabled = enabled; for (const item of this.getUI().leftActionBar.viewItems) { - (item as ActionViewItem).getAction().enabled = enabled; + (item as ActionViewItem).action.enabled = enabled; } for (const item of this.getUI().rightActionBar.viewItems) { - (item as ActionViewItem).getAction().enabled = enabled; + (item as ActionViewItem).action.enabled = enabled; } this.getUI().checkAll.disabled = !enabled; // this.getUI().inputBox.enabled = enabled; Avoid loosing focus. diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index bdcb5114917..25058c9de25 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -17,14 +17,15 @@ import { compareAnything } from 'vs/base/common/comparers'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels'; +import { IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels'; import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; +import { ltrim } from 'vs/base/common/strings'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; -import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; +import { QuickPickItem, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; import 'vs/css!./media/quickInput'; import { localize } from 'vs/nls'; @@ -35,6 +36,7 @@ interface IListElement { readonly index: number; readonly item: IQuickPickItem; readonly saneLabel: string; + readonly saneSortLabel: string; readonly saneMeta?: string; readonly saneAriaLabel: string; readonly saneDescription?: string; @@ -52,6 +54,7 @@ class ListElement implements IListElement, IDisposable { index!: number; item!: IQuickPickItem; saneLabel!: string; + saneSortLabel!: string; saneMeta!: string; saneAriaLabel!: string; saneDescription?: string; @@ -250,12 +253,13 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; private list: List; - private inputElements: Array = []; + private inputElements: Array = []; private elements: ListElement[] = []; private elementsToIndexes = new Map(); matchOnDescription = false; matchOnDetail = false; matchOnLabel = true; + matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy'; matchOnMeta = true; sortByLabel = true; private readonly _onChangedAllVisibleChecked = new Emitter(); @@ -432,7 +436,7 @@ export class QuickInputList { } } - setElements(inputElements: Array): void { + setElements(inputElements: Array): void { this.elementDisposables = dispose(this.elementDisposables); const fireButtonTriggered = (event: IQuickPickItemButtonEvent) => this.fireButtonTriggered(event); this.inputElements = inputElements; @@ -440,6 +444,7 @@ export class QuickInputList { if (item.type !== 'separator') { const previous = index && inputElements[index - 1]; const saneLabel = item.label && item.label.replace(/\r?\n/g, ' '); + const saneSortLabel = parseLabelWithIcons(saneLabel).text.trim(); const saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' '); const saneDescription = item.description && item.description.replace(/\r?\n/g, ' '); const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' '); @@ -454,6 +459,7 @@ export class QuickInputList { index, item, saneLabel, + saneSortLabel, saneMeta, saneAriaLabel, saneDescription, @@ -606,6 +612,8 @@ export class QuickInputList { this.list.layout(); return false; } + + const queryWithWhitespace = query; query = query.trim(); // Reset filtering @@ -624,7 +632,12 @@ export class QuickInputList { else { let currentSeparator: IQuickPickSeparator | undefined; this.elements.forEach(element => { - const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined; + let labelHighlights: IMatch[] | undefined; + if (this.matchOnLabelMode === 'fuzzy') { + labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined; + } else { + labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined; + } const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined; const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined; const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined; @@ -722,6 +735,43 @@ export class QuickInputList { } } +export function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null { + + const { text, iconOffsets } = target; + + // Return early if there are no icon markers in the word to match against + if (!iconOffsets || iconOffsets.length === 0) { + return matchesContiguous(query, text); + } + + // Trim the word to match against because it could have leading + // whitespace now if the word started with an icon + const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' '); + const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length; + + // match on value without icon + const matches = matchesContiguous(query, wordToMatchAgainstWithoutIconsTrimmed); + + // Map matches back to offsets with icon and trimming + if (matches) { + for (const match of matches) { + const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */; + match.start += iconOffset; + match.end += iconOffset; + } + } + + return matches; +} + +function matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null { + const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase()); + if (matchIndex !== -1) { + return [{ start: matchIndex, end: matchIndex + word.length }]; + } + return null; +} + function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number { const labelHighlightsA = elementA.labelHighlights || []; @@ -738,7 +788,7 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return 0; } - return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); + return compareAnything(elementA.saneSortLabel, elementB.saneSortLabel, lookFor); } class QuickInputAccessibilityProvider implements IListAccessibilityProvider { diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index bf9979e00b1..bdacf12b590 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -18,6 +18,8 @@ export interface IQuickPickItemHighlights { detail?: IMatch[]; } +export type QuickPickItem = IQuickPickSeparator | IQuickPickItem; + export interface IQuickPickItem { type?: 'item'; id?: string; @@ -292,6 +294,13 @@ export interface IQuickPick extends IQuickInput { matchOnLabel: boolean; + /** + * The mode to filter label with. Fuzzy will use fuzzy searching and + * contiguous will make filter entries that do not contain the exact string + * (including whitespace). This defaults to `'fuzzy'`. + */ + matchOnLabelMode: 'fuzzy' | 'contiguous'; + sortByLabel: boolean; autoFocusOnList: boolean; diff --git a/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts b/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts index 89f2f212dd4..dc99756b291 100644 --- a/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/base/parts/quickinput/test/browser/quickinput.test.ts @@ -39,7 +39,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 controller = new QuickInputController({ container: fixture, idPrefix: 'testQuickInput', - ignoreFocusOut() { return false; }, + ignoreFocusOut() { return true; }, isScreenReaderOptimized() { return false; }, returnFocus() { }, backKeybindingLabel() { return undefined; }, @@ -80,7 +80,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 await wait; controller.accept(); - const pick = await pickPromise; + const pick = await raceTimeout(pickPromise, 2000); assert.strictEqual(pick, item); }); @@ -104,7 +104,7 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 await wait; controller.accept(); - const value = await inputPromise; + const value = await raceTimeout(inputPromise, 2000); assert.strictEqual(value, 'foo'); }); diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index 3e25c4097f1..53e38ce14c2 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -145,7 +145,7 @@ /** * @param {string} channel * @param {any[]} args - * @returns {Promise | undefined} + * @returns {Promise | never} */ invoke(channel, ...args) { if (validateIPC(channel)) { @@ -156,7 +156,7 @@ /** * @param {string} channel * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener - * @returns {IpcRenderer} + * @returns {IpcRenderer | never} */ on(channel, listener) { if (validateIPC(channel)) { @@ -169,7 +169,7 @@ /** * @param {string} channel * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener - * @returns {IpcRenderer} + * @returns {IpcRenderer | never} */ once(channel, listener) { if (validateIPC(channel)) { @@ -182,7 +182,7 @@ /** * @param {string} channel * @param {(event: IpcRendererEvent, ...args: any[]) => void} listener - * @returns {IpcRenderer} + * @returns {IpcRenderer | never} */ removeListener(channel, listener) { if (validateIPC(channel)) { diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 7d9b29a6e3a..d89c22a4f0d 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -36,6 +36,10 @@ export interface ISandboxNodeProcess extends INodeProcess { /** * The `process.pid` property returns the PID of the process. + * + * @deprecated this property will be removed once sandbox is enabled. + * + * TODO@bpasero remove this property when sandbox is on */ readonly pid: number; diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index a6bfeaefbd9..8e42b693a93 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -357,13 +357,9 @@ export class InMemoryStorageDatabase implements IStorageDatabase { } async updateItems(request: IUpdateRequest): Promise { - if (request.insert) { - request.insert.forEach((value, key) => this.items.set(key, value)); - } + request.insert?.forEach((value, key) => this.items.set(key, value)); - if (request.delete) { - request.delete.forEach(key => this.items.delete(key)); - } + request.delete?.forEach(key => this.items.delete(key)); } async close(): Promise { } diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 789481987b9..f4cf5ac78e1 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -301,9 +301,9 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return new Promise((resolve, reject) => { import('@vscode/sqlite3').then(sqlite3 => { const connection: IDatabaseConnection = { - db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => { + db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, (error: (Error & { code?: string }) | null) => { if (error) { - return connection.db ? connection.db.close(() => reject(error)) : reject(error); + return (connection.db && error.code !== 'SQLITE_CANTOPEN' /* https://github.com/TryGhost/node-sqlite3/issues/1617 */) ? connection.db.close(() => reject(error)) : reject(error); } // The following exec() statement serves two purposes: diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index c7652a95df9..70ff91977be 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -670,56 +670,57 @@ flakySuite('SQLite Storage Library', function () { }); test('multiple concurrent writes execute in sequence', async () => { - - class TestStorage extends Storage { - getStorage(): IStorageDatabase { - return this.database; + return runWithFakedTimers({}, async () => { + class TestStorage extends Storage { + getStorage(): IStorageDatabase { + return this.database; + } } - } - const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db'))); + const storage = new TestStorage(new SQLiteStorageDatabase(join(testdir, 'storage.db'))); - await storage.init(); + await storage.init(); - storage.set('foo', 'bar'); - storage.set('some/foo/path', 'some/bar/path'); + storage.set('foo', 'bar'); + storage.set('some/foo/path', 'some/bar/path'); - await timeout(2); + await timeout(2); - storage.set('foo1', 'bar'); - storage.set('some/foo1/path', 'some/bar/path'); + storage.set('foo1', 'bar'); + storage.set('some/foo1/path', 'some/bar/path'); - await timeout(2); + await timeout(2); - storage.set('foo2', 'bar'); - storage.set('some/foo2/path', 'some/bar/path'); + storage.set('foo2', 'bar'); + storage.set('some/foo2/path', 'some/bar/path'); - await timeout(2); + await timeout(2); - storage.delete('foo1'); - storage.delete('some/foo1/path'); + storage.delete('foo1'); + storage.delete('some/foo1/path'); - await timeout(2); + await timeout(2); - storage.delete('foo4'); - storage.delete('some/foo4/path'); + storage.delete('foo4'); + storage.delete('some/foo4/path'); - await timeout(5); + await timeout(5); - storage.set('foo3', 'bar'); - await storage.set('some/foo3/path', 'some/bar/path'); + storage.set('foo3', 'bar'); + await storage.set('some/foo3/path', 'some/bar/path'); - const items = await storage.getStorage().getItems(); - strictEqual(items.get('foo'), 'bar'); - strictEqual(items.get('some/foo/path'), 'some/bar/path'); - strictEqual(items.has('foo1'), false); - strictEqual(items.has('some/foo1/path'), false); - strictEqual(items.get('foo2'), 'bar'); - strictEqual(items.get('some/foo2/path'), 'some/bar/path'); - strictEqual(items.get('foo3'), 'bar'); - strictEqual(items.get('some/foo3/path'), 'some/bar/path'); + const items = await storage.getStorage().getItems(); + strictEqual(items.get('foo'), 'bar'); + strictEqual(items.get('some/foo/path'), 'some/bar/path'); + strictEqual(items.has('foo1'), false); + strictEqual(items.has('some/foo1/path'), false); + strictEqual(items.get('foo2'), 'bar'); + strictEqual(items.get('some/foo2/path'), 'some/bar/path'); + strictEqual(items.get('foo3'), 'bar'); + strictEqual(items.get('some/foo3/path'), 'some/bar/path'); - await storage.close(); + await storage.close(); + }); }); test('lots of INSERT & DELETE (below inline max)', async () => { @@ -773,4 +774,18 @@ flakySuite('SQLite Storage Library', function () { await storage.close(); }); + + test('invalid path does not hang', async () => { + const storage = new SQLiteStorageDatabase(join(testdir, 'nonexist', 'storage.db')); + + let error; + try { + await storage.getItems(); + await storage.close(); + } catch (e) { + error = e; + } + + ok(error); + }); }); diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 435f0066b9c..1ad53561927 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as dom from 'vs/base/browser/dom'; -const $ = dom.$; +import { $, h, multibyteAwareBtoa } from 'vs/base/browser/dom'; suite('dom', () => { test('hasClass', () => { @@ -73,9 +72,9 @@ suite('dom', () => { }); test('multibyteAwareBtoa', () => { - assert.ok(dom.multibyteAwareBtoa('hello world').length > 0); - assert.ok(dom.multibyteAwareBtoa('åšŗäģŽå').length > 0); - assert.ok(dom.multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013 + assert.ok(multibyteAwareBtoa('hello world').length > 0); + assert.ok(multibyteAwareBtoa('åšŗäģŽå').length > 0); + assert.ok(multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013 }); suite('$', () => { @@ -129,4 +128,152 @@ suite('dom', () => { assert.strictEqual(firstChild.textContent, 'foobar'); }); }); + + suite('h', () => { + test('should build simple nodes', () => { + const div = h('div'); + assert(div.root instanceof HTMLElement); + assert.strictEqual(div.root.tagName, 'DIV'); + + const span = h('span'); + assert(span.root instanceof HTMLElement); + assert.strictEqual(span.root.tagName, 'SPAN'); + + const img = h('img'); + assert(img.root instanceof HTMLElement); + assert.strictEqual(img.root.tagName, 'IMG'); + }); + + test('should handle ids and classes', () => { + const divId = h('div#myid'); + assert.strictEqual(divId.root.tagName, 'DIV'); + assert.strictEqual(divId.root.id, 'myid'); + + const divClass = h('div.a'); + assert.strictEqual(divClass.root.tagName, 'DIV'); + assert.strictEqual(divClass.root.classList.length, 1); + assert(divClass.root.classList.contains('a')); + + const divClasses = h('div.a.b.c'); + assert.strictEqual(divClasses.root.tagName, 'DIV'); + assert.strictEqual(divClasses.root.classList.length, 3); + assert(divClasses.root.classList.contains('a')); + assert(divClasses.root.classList.contains('b')); + assert(divClasses.root.classList.contains('c')); + + const divAll = h('div#myid.a.b.c'); + assert.strictEqual(divAll.root.tagName, 'DIV'); + assert.strictEqual(divAll.root.id, 'myid'); + assert.strictEqual(divAll.root.classList.length, 3); + assert(divAll.root.classList.contains('a')); + assert(divAll.root.classList.contains('b')); + assert(divAll.root.classList.contains('c')); + + const spanId = h('span#myid'); + assert.strictEqual(spanId.root.tagName, 'SPAN'); + assert.strictEqual(spanId.root.id, 'myid'); + + const spanClass = h('span.a'); + assert.strictEqual(spanClass.root.tagName, 'SPAN'); + assert.strictEqual(spanClass.root.classList.length, 1); + assert(spanClass.root.classList.contains('a')); + + const spanClasses = h('span.a.b.c'); + assert.strictEqual(spanClasses.root.tagName, 'SPAN'); + assert.strictEqual(spanClasses.root.classList.length, 3); + assert(spanClasses.root.classList.contains('a')); + assert(spanClasses.root.classList.contains('b')); + assert(spanClasses.root.classList.contains('c')); + + const spanAll = h('span#myid.a.b.c'); + assert.strictEqual(spanAll.root.tagName, 'SPAN'); + assert.strictEqual(spanAll.root.id, 'myid'); + assert.strictEqual(spanAll.root.classList.length, 3); + assert(spanAll.root.classList.contains('a')); + assert(spanAll.root.classList.contains('b')); + assert(spanAll.root.classList.contains('c')); + }); + + test('should implicitly handle ids and classes', () => { + const divId = h('#myid'); + assert.strictEqual(divId.root.tagName, 'DIV'); + assert.strictEqual(divId.root.id, 'myid'); + + const divClass = h('.a'); + assert.strictEqual(divClass.root.tagName, 'DIV'); + assert.strictEqual(divClass.root.classList.length, 1); + assert(divClass.root.classList.contains('a')); + + const divClasses = h('.a.b.c'); + assert.strictEqual(divClasses.root.tagName, 'DIV'); + assert.strictEqual(divClasses.root.classList.length, 3); + assert(divClasses.root.classList.contains('a')); + assert(divClasses.root.classList.contains('b')); + assert(divClasses.root.classList.contains('c')); + + const divAll = h('#myid.a.b.c'); + assert.strictEqual(divAll.root.tagName, 'DIV'); + assert.strictEqual(divAll.root.id, 'myid'); + assert.strictEqual(divAll.root.classList.length, 3); + assert(divAll.root.classList.contains('a')); + assert(divAll.root.classList.contains('b')); + assert(divAll.root.classList.contains('c')); + }); + + test('should handle @ identifiers', () => { + const implicit = h('@el'); + assert.strictEqual(implicit.root, implicit.el); + assert.strictEqual(implicit.el.tagName, 'DIV'); + + const explicit = h('div@el'); + assert.strictEqual(explicit.root, explicit.el); + assert.strictEqual(explicit.el.tagName, 'DIV'); + + const implicitId = h('#myid@el'); + assert.strictEqual(implicitId.root, implicitId.el); + assert.strictEqual(implicitId.el.tagName, 'DIV'); + assert.strictEqual(implicitId.root.id, 'myid'); + + const explicitId = h('div#myid@el'); + assert.strictEqual(explicitId.root, explicitId.el); + assert.strictEqual(explicitId.el.tagName, 'DIV'); + assert.strictEqual(explicitId.root.id, 'myid'); + + const implicitClass = h('.a@el'); + assert.strictEqual(implicitClass.root, implicitClass.el); + assert.strictEqual(implicitClass.el.tagName, 'DIV'); + assert.strictEqual(implicitClass.root.classList.length, 1); + assert(implicitClass.root.classList.contains('a')); + + const explicitClass = h('div.a@el'); + assert.strictEqual(explicitClass.root, explicitClass.el); + assert.strictEqual(explicitClass.el.tagName, 'DIV'); + assert.strictEqual(explicitClass.root.classList.length, 1); + assert(explicitClass.root.classList.contains('a')); + }); + }); + + test('should recurse', () => { + const result = h('div.code-view', [ + h('div.title@title'), + h('div.container', [ + h('div.gutter@gutterDiv'), + h('span@editor'), + ]), + ]); + + assert.strictEqual(result.root.tagName, 'DIV'); + assert.strictEqual(result.root.className, 'code-view'); + assert.strictEqual(result.root.childElementCount, 2); + assert.strictEqual(result.root.firstElementChild, result.title); + assert.strictEqual(result.title.tagName, 'DIV'); + assert.strictEqual(result.title.className, 'title'); + assert.strictEqual(result.title.childElementCount, 0); + assert.strictEqual(result.gutterDiv.tagName, 'DIV'); + assert.strictEqual(result.gutterDiv.className, 'gutter'); + assert.strictEqual(result.gutterDiv.childElementCount, 0); + assert.strictEqual(result.editor.tagName, 'SPAN'); + assert.strictEqual(result.editor.className, ''); + assert.strictEqual(result.editor.childElementCount, 0); + }); }); diff --git a/src/vs/base/test/browser/indexedDB.test.ts b/src/vs/base/test/browser/indexedDB.test.ts index 581b6719148..ba9dcd0f51c 100644 --- a/src/vs/base/test/browser/indexedDB.test.ts +++ b/src/vs/base/test/browser/indexedDB.test.ts @@ -16,9 +16,7 @@ flakySuite('IndexedDB', () => { }); teardown(() => { - if (indexedDB) { - indexedDB.close(); - } + indexedDB?.close(); }); test('runInTransaction', async () => { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 85743c645f3..f30fc44b962 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -68,7 +68,7 @@ suite('MarkdownRenderer', () => { }); suite('Code block renderer', () => { - const simpleCodeBlockRenderer = (code: string): Promise => { + const simpleCodeBlockRenderer = (lang: string, code: string): Promise => { const element = document.createElement('code'); element.textContent = code; return Promise.resolve(element); @@ -92,7 +92,7 @@ suite('MarkdownRenderer', () => { codeBlockRenderer: simpleCodeBlockRenderer }); result.dispose(); - setTimeout(resolve, 250); + setTimeout(resolve, 50); }); }); @@ -111,10 +111,23 @@ suite('MarkdownRenderer', () => { setTimeout(() => { result.dispose(); resolveCodeBlockRendering(document.createElement('code')); - setTimeout(resolve, 250); - }, 250); + setTimeout(resolve, 50); + }, 50); }); }); + + test('Code blocks should use leading language id (#157793)', async () => { + const markdown = { value: '```js some other stuff\n1 + 1;\n```' }; + const lang = await new Promise(resolve => { + renderMarkdown(markdown, { + codeBlockRenderer: async (lang, value) => { + resolve(lang); + return simpleCodeBlockRenderer(lang, value); + } + }); + }); + assert.strictEqual(lang, 'js'); + }); }); suite('ThemeIcons Support On', () => { @@ -173,6 +186,14 @@ suite('MarkdownRenderer', () => { `); }); + + test('render icon in without href (#152170)', () => { + const mds = new MarkdownString(undefined, { supportThemeIcons: true, supportHtml: true }); + mds.appendMarkdown(`$(sync)`); + + const result: HTMLElement = renderMarkdown(mds).element; + assert.strictEqual(result.innerHTML, `

`); + }); }); suite('ThemeIcons Support Off', () => { 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 2fc37090431..d10d76b0b85 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -8,6 +8,7 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list 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'; interface Element { id: string; @@ -435,4 +436,60 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']); }); + + test('issue #121567', async () => { + const container = document.createElement('div'); + + const calls: Element[] = []; + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + async getChildren(element: Element) { + calls.push(element); + return element.children ?? Iterable.empty(); + } + }; + + const model = new Model({ + id: 'root', + children: [{ + id: 'a', children: [{ + id: 'aa' + }] + }] + }); + const a = model.get('a'); + + const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() }); + tree.layout(200); + + await tree.setInput(model.root); + assert.strictEqual(calls.length, 1, 'There should be a single getChildren call for the root'); + assert(tree.isCollapsible(a), 'a is collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should be no changes to the calls list, since a was collapsed'); + assert(tree.isCollapsible(a), 'a is collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + const children = a.children; + a.children = []; + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should still be no changes to the calls list, since a was collapsed'); + assert(!tree.isCollapsible(a), 'a is no longer collapsible'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + a.children = children; + await tree.updateChildren(a, false); + assert.strictEqual(calls.length, 1, 'There should still be no changes to the calls list, since a was collapsed'); + assert(tree.isCollapsible(a), 'a is collapsible again'); + assert(tree.isCollapsed(a), 'a is collapsed'); + + await tree.expand(a); + assert.strictEqual(calls.length, 2, 'Finally, there should be a getChildren call for a'); + assert(tree.isCollapsible(a), 'a is still collapsible'); + assert(!tree.isCollapsed(a), 'a is expanded'); + }); }); 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 91e83ec4476..9ae0e08b0f1 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -358,7 +358,7 @@ suite('IndexTreeModel', () => { assert.deepStrictEqual(list.length, 3); - model.setCollapsed([0], false); + model.expandTo([0, 1]); assert.deepStrictEqual(list.length, 6); assert.deepStrictEqual(list[0].element, 0); assert.deepStrictEqual(list[0].collapsed, false); diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 4519661b5c9..23e2b720421 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -6,6 +6,19 @@ import * as assert from 'assert'; import * as arrays from 'vs/base/common/arrays'; suite('Arrays', () => { + + test('removeFastWithoutKeepingOrder', () => { + const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69]; + arrays.removeFastWithoutKeepingOrder(array, 1); + assert.deepStrictEqual(array, [1, 69, 5, 7, 55, 59, 60, 61, 64]); + + arrays.removeFastWithoutKeepingOrder(array, 0); + assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60, 61]); + + arrays.removeFastWithoutKeepingOrder(array, 7); + assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60]); + }); + test('findFirst', () => { const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69]; diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index 2cae56c40ef..9dfe59a58fe 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -8,30 +8,6 @@ import * as collections from 'vs/base/common/collections'; suite('Collections', () => { - test('forEach', () => { - collections.forEach({}, () => assert(false)); - collections.forEach(Object.create(null), () => assert(false)); - - let count = 0; - collections.forEach({ toString: 123 }, () => count++); - assert.strictEqual(count, 1); - - count = 0; - const dict = Object.create(null); - dict['toString'] = 123; - collections.forEach(dict, () => count++); - assert.strictEqual(count, 1); - - collections.forEach(dict, () => false); - - collections.forEach(dict, (x, remove) => remove()); - assert.strictEqual(dict['toString'], undefined); - - // don't iterate over properties that are not on the object itself - const test = Object.create({ 'derived': true }); - collections.forEach(test, () => assert(false)); - }); - test('groupBy', () => { const group1 = 'a', group2 = 'b'; diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index a34566c10f1..39d878bce5d 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -8,7 +8,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event'; import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle'; -import { DisposableTracker } from 'vs/base/test/common/utils'; +import { observableValue, transaction } from 'vs/base/common/observable'; +import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; namespace Samples { @@ -624,6 +625,33 @@ suite('PausableEmitter', function () { }); }); +suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + test('fromObservable', function () { + + const obs = observableValue('test', 12); + const event = Event.fromObservable(obs); + + const values: number[] = []; + const d = event(n => { values.push(n); }); + + obs.set(3, undefined); + obs.set(13, undefined); + obs.set(3, undefined); + obs.set(33, undefined); + obs.set(1, undefined); + + transaction(tx => { + obs.set(334, tx); + obs.set(99, tx); + }); + + assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99])); + d.dispose(); + }); +}); + suite('Event utils', () => { suite('EventBufferer', () => { diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 8b890f21662..29754cc4eed 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -185,24 +185,28 @@ suite('Filters', () => { assert(matchesWords('Debug Console', 'Open: Debug Console')); filterOk(matchesWords, 'gp', 'Git: Pull', [{ start: 0, end: 1 }, { start: 5, end: 6 }]); - filterOk(matchesWords, 'g p', 'Git: Pull', [{ start: 0, end: 1 }, { start: 3, end: 4 }, { start: 5, end: 6 }]); + filterOk(matchesWords, 'g p', 'Git: Pull', [{ start: 0, end: 1 }, { start: 5, end: 6 }]); filterOk(matchesWords, 'gipu', 'Git: Pull', [{ start: 0, end: 2 }, { start: 5, end: 7 }]); filterOk(matchesWords, 'gp', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 15, end: 16 }]); - filterOk(matchesWords, 'g p', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 13, end: 14 }, { start: 15, end: 16 }]); + filterOk(matchesWords, 'g p', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 15, end: 16 }]); filterOk(matchesWords, 'gipu', 'Category: Git: Pull', [{ start: 10, end: 12 }, { start: 15, end: 17 }]); filterNotOk(matchesWords, 'it', 'Git: Pull'); filterNotOk(matchesWords, 'll', 'Git: Pull'); filterOk(matchesWords, 'git: プãƒĢ', 'git: プãƒĢ', [{ start: 0, end: 7 }]); - filterOk(matchesWords, 'git プãƒĢ', 'git: プãƒĢ', [{ start: 0, end: 4 }, { start: 5, end: 7 }]); + filterOk(matchesWords, 'git プãƒĢ', 'git: プãƒĢ', [{ start: 0, end: 3 }, { start: 5, end: 7 }]); filterOk(matchesWords, 'Ãļäk', 'Öhm: Älles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]); // Handles issue #123915 filterOk(matchesWords, 'C++', 'C/C++: command', [{ start: 2, end: 5 }]); + // Handles issue #154533 + filterOk(matchesWords, '.', ':', []); + filterOk(matchesWords, '.', '.', [{ start: 0, end: 1 }]); + // assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null); // assert.deepStrictEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 3a979de3229..896d06ec812 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -852,12 +852,12 @@ suite('Map', () => { for (const item of keys) { tst.set(item, true); - assert.ok(tst._isBalanced()); + assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`); } for (const item of keys) { tst.delete(item); - assert.ok(tst._isBalanced()); + assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`); } } }); diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts new file mode 100644 index 00000000000..a3460dca22b --- /dev/null +++ b/src/vs/base/test/common/observable.test.ts @@ -0,0 +1,507 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; + +suite('observable integration', () => { + test('basic observable + autorun', () => { + const log = new Log(); + const observable = observableValue('MyObservableValue', 0); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${observable.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + transaction((tx) => { + observable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); + }); + + test('basic computed + autorun', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 0 = 1', + 'value: 1', + ]); + + observable2.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 1 = 2', + 'value: 2', + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 5 + 5 = 10', + 'value: 10', + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed.read(reader)}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); + + transaction((tx) => { + observable1.set(-1, tx); + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 0 = -1', + 'computed is -1', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + + observable2.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 1 = 0', + 'value: 0', + ]); + }); + + test('topological order', () => { + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const observable2 = observableValue('MyObservableValue2', 0); + + const computed1 = derived('computed1', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + const computed2 = derived('computed2', (reader) => { + const value1 = computed1.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + const computed3 = derived('computed3', (reader) => { + const value1 = computed2.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + autorun('MyAutorun', (reader) => { + log.log(`value: ${computed3.read(reader)}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 0 + 0 = 0', + 'recompute2: 0 + 0 + 0 = 0', + 'recompute3: 0 + 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 + 0 = 1', + 'recompute2: 1 + 1 + 0 = 2', + 'recompute3: 2 + 1 + 0 = 3', + 'value: 3', + ]); + + transaction((tx) => { + observable1.set(2, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 2 + 0 = 2', + 'recompute2: 2 + 2 + 0 = 4', + 'computed2: 4', + ]); + + observable1.set(3, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 3 + 0 = 3', + 'recompute2: 3 + 3 + 0 = 6', + 'computed2: 6', + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute3: 6 + 3 + 0 = 9', + 'value: 9', + ]); + }); + + test('self-disposing autorun', () => { + const log = new Log(); + + const observable1 = new LoggingObservableValue('MyObservableValue1', 0, log); + const observable2 = new LoggingObservableValue('MyObservableValue2', 0, log); + const observable3 = new LoggingObservableValue('MyObservableValue3', 0, log); + + const d = autorun('autorun', (reader) => { + if (observable1.read(reader) >= 2) { + observable2.read(reader); + d.dispose(); + observable3.read(reader); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.firstObserverAdded', + 'MyObservableValue1.get', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.set (value 1)', + 'MyObservableValue1.get', + ]); + + observable1.set(2, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'MyObservableValue1.set (value 2)', + 'MyObservableValue1.get', + 'MyObservableValue2.firstObserverAdded', + 'MyObservableValue2.get', + 'MyObservableValue1.lastObserverRemoved', + 'MyObservableValue2.lastObserverRemoved', + 'MyObservableValue3.get', + ]); + }); + + test('from event', () => { + const log = new Log(); + + let value = 0; + const eventEmitter = new Emitter(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + + const autorunDisposable = autorun('MyAutorun', (reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + value = 1; + eventEmitter.fire(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + // Maybe this scenario should not be supported. + + const log = new Log(); + const observable1 = observableValue('MyObservableValue1', 0); + const computed1 = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derived('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + }); +}); + +suite('observable details', () => { + test('1', () => { + const log = new Log(); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + const observable = new LoggingObservableValue('observable', 0, log); + const computed = derived('test', reader => { + if (shouldReadObservable.read(reader)) { + return observable.read(reader) * 2; + } + return 1; + }); + autorun('test', reader => { + const value = computed.read(reader); + log.log(`autorun: ${value}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.firstObserverAdded", "observable.get", "autorun: 0"])); + + transaction(tx => { + observable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + computed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.lastObserverRemoved"])); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); + }); +}); + +export class LoggingObserver implements IObserver { + private count = 0; + + constructor(public readonly debugName: string, private readonly log: Log) { + } + + beginUpdate(observable: IObservable): void { + this.count++; + this.log.log(`${this.debugName}.beginUpdate (count ${this.count})`); + } + handleChange(observable: IObservable, change: TChange): void { + this.log.log(`${this.debugName}.handleChange (count ${this.count})`); + } + endUpdate(observable: IObservable): void { + this.log.log(`${this.debugName}.endUpdate (count ${this.count})`); + this.count--; + } +} + +export class LoggingObservableValue + extends BaseObservable + implements ISettableObservable +{ + private value: T; + + constructor(public readonly debugName: string, initialValue: T, private readonly log: Log) { + super(); + this.value = initialValue; + } + + protected override onFirstObserverAdded(): void { + this.log.log(`${this.debugName}.firstObserverAdded`); + } + + protected override onLastObserverRemoved(): void { + this.log.log(`${this.debugName}.lastObserverRemoved`); + } + + public get(): T { + this.log.log(`${this.debugName}.get`); + return this.value; + } + + public set(value: T, tx: ITransaction | undefined, change: TChange): void { + if (this.value === value) { + return; + } + + if (!tx) { + transaction((tx) => { + this.set(value, tx, change); + }, () => `Setting ${this.debugName}`); + return; + } + + this.log.log(`${this.debugName}.set (value ${value})`); + + this.value = value; + + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } + + override toString(): string { + return `${this.debugName}: ${this.value}`; + } +} + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index cb7dedbe272..1f5e7d0b812 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -82,24 +82,6 @@ suite('Types', () => { assert(types.isEmptyObject({})); }); - test('isArray', () => { - assert(!types.isArray(undefined)); - assert(!types.isArray(null)); - assert(!types.isArray('foo')); - assert(!types.isArray(5)); - assert(!types.isArray(true)); - assert(!types.isArray({})); - assert(!types.isArray(/test/)); - assert(!types.isArray(new RegExp(''))); - assert(!types.isArray(new Date())); - assert(!types.isArray(assert)); - assert(!types.isArray(function foo() { /**/ })); - assert(!types.isArray({ foo: 'bar' })); - - assert(types.isArray([])); - assert(types.isArray([1, 2, '3'])); - }); - test('isString', () => { assert(!types.isString(undefined)); assert(!types.isString(null)); diff --git a/src/vs/base/test/node/css.build.test.ts b/src/vs/base/test/node/css.build.test.ts new file mode 100644 index 00000000000..9e7323b7f7a --- /dev/null +++ b/src/vs/base/test/node/css.build.test.ts @@ -0,0 +1,313 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { CSSPluginUtilities, rewriteUrls } from 'vs/css.build'; + +suite('CSSPlugin', () => { + + test('Utilities.pathOf', () => { + assert.strictEqual(CSSPluginUtilities.pathOf(''), ''); + assert.strictEqual(CSSPluginUtilities.pathOf('/a'), '/'); + assert.strictEqual(CSSPluginUtilities.pathOf('a/b/c.css'), 'a/b/'); + assert.strictEqual(CSSPluginUtilities.pathOf('a'), ''); + assert.strictEqual(CSSPluginUtilities.pathOf('a.com/a.css'), 'a.com/'); + assert.strictEqual(CSSPluginUtilities.pathOf('http://a.com/a.css'), 'http://a.com/'); + assert.strictEqual(CSSPluginUtilities.pathOf('https://a.com/a.css'), 'https://a.com/'); + assert.strictEqual(CSSPluginUtilities.pathOf('http://a.com/a/b/c.css'), 'http://a.com/a/b/'); + assert.strictEqual(CSSPluginUtilities.pathOf('https://a.com/a/b/c.css'), 'https://a.com/a/b/'); + assert.strictEqual(CSSPluginUtilities.pathOf('/a.css'), '/'); + assert.strictEqual(CSSPluginUtilities.pathOf('/a/b/c.css'), '/a/b/'); + }); + + test('Utilities.joinPaths', () => { + function mytest(a: string, b: string, expected: string) { + assert.strictEqual(CSSPluginUtilities.joinPaths(a, b), expected, '<' + a + '> + <' + b + '> = <' + expected + '>'); + } + mytest('', 'a.css', 'a.css'); + mytest('', './a.css', 'a.css'); + mytest('', '././././a.css', 'a.css'); + mytest('', './../a.css', '../a.css'); + mytest('', '../../a.css', '../../a.css'); + mytest('', '../../a/b/c.css', '../../a/b/c.css'); + mytest('/', 'a.css', '/a.css'); + mytest('/', './a.css', '/a.css'); + mytest('/', '././././a.css', '/a.css'); + mytest('/', './../a.css', '/a.css'); + mytest('/', '../../a.css', '/a.css'); + mytest('/', '../../a/b/c.css', '/a/b/c.css'); + mytest('x/y/z/', 'a.css', 'x/y/z/a.css'); + mytest('x/y/z/', './a.css', 'x/y/z/a.css'); + mytest('x/y/z/', '././././a.css', 'x/y/z/a.css'); + mytest('x/y/z/', './../a.css', 'x/y/a.css'); + mytest('x/y/z/', '../../a.css', 'x/a.css'); + mytest('x/y/z/', '../../a/b/c.css', 'x/a/b/c.css'); + + mytest('//a.com/', 'a.css', '//a.com/a.css'); + mytest('//a.com/', './a.css', '//a.com/a.css'); + mytest('//a.com/', '././././a.css', '//a.com/a.css'); + mytest('//a.com/', './../a.css', '//a.com/a.css'); + mytest('//a.com/', '../../a.css', '//a.com/a.css'); + mytest('//a.com/', '../../a/b/c.css', '//a.com/a/b/c.css'); + mytest('//a.com/x/y/z/', 'a.css', '//a.com/x/y/z/a.css'); + mytest('//a.com/x/y/z/', './a.css', '//a.com/x/y/z/a.css'); + mytest('//a.com/x/y/z/', '././././a.css', '//a.com/x/y/z/a.css'); + mytest('//a.com/x/y/z/', './../a.css', '//a.com/x/y/a.css'); + mytest('//a.com/x/y/z/', '../../a.css', '//a.com/x/a.css'); + mytest('//a.com/x/y/z/', '../../a/b/c.css', '//a.com/x/a/b/c.css'); + + mytest('http://a.com/', 'a.css', 'http://a.com/a.css'); + mytest('http://a.com/', './a.css', 'http://a.com/a.css'); + mytest('http://a.com/', '././././a.css', 'http://a.com/a.css'); + mytest('http://a.com/', './../a.css', 'http://a.com/a.css'); + mytest('http://a.com/', '../../a.css', 'http://a.com/a.css'); + mytest('http://a.com/', '../../a/b/c.css', 'http://a.com/a/b/c.css'); + mytest('http://a.com/x/y/z/', 'a.css', 'http://a.com/x/y/z/a.css'); + mytest('http://a.com/x/y/z/', './a.css', 'http://a.com/x/y/z/a.css'); + mytest('http://a.com/x/y/z/', '././././a.css', 'http://a.com/x/y/z/a.css'); + mytest('http://a.com/x/y/z/', './../a.css', 'http://a.com/x/y/a.css'); + mytest('http://a.com/x/y/z/', '../../a.css', 'http://a.com/x/a.css'); + mytest('http://a.com/x/y/z/', '../../a/b/c.css', 'http://a.com/x/a/b/c.css'); + + mytest('https://a.com/', 'a.css', 'https://a.com/a.css'); + mytest('https://a.com/', './a.css', 'https://a.com/a.css'); + mytest('https://a.com/', '././././a.css', 'https://a.com/a.css'); + mytest('https://a.com/', './../a.css', 'https://a.com/a.css'); + mytest('https://a.com/', '../../a.css', 'https://a.com/a.css'); + mytest('https://a.com/', '../../a/b/c.css', 'https://a.com/a/b/c.css'); + mytest('https://a.com/x/y/z/', 'a.css', 'https://a.com/x/y/z/a.css'); + mytest('https://a.com/x/y/z/', './a.css', 'https://a.com/x/y/z/a.css'); + mytest('https://a.com/x/y/z/', '././././a.css', 'https://a.com/x/y/z/a.css'); + mytest('https://a.com/x/y/z/', './../a.css', 'https://a.com/x/y/a.css'); + mytest('https://a.com/x/y/z/', '../../a.css', 'https://a.com/x/a.css'); + mytest('https://a.com/x/y/z/', '../../a/b/c.css', 'https://a.com/x/a/b/c.css'); + }); + + test('Utilities.commonPrefix', () => { + function mytest(a: string, b: string, expected: string) { + assert.strictEqual(CSSPluginUtilities.commonPrefix(a, b), expected, 'prefix(<' + a + '>, <' + b + '>) = <' + expected + '>'); + assert.strictEqual(CSSPluginUtilities.commonPrefix(b, a), expected, 'prefix(<' + b + '>, <' + a + '>) = <' + expected + '>'); + } + mytest('', '', ''); + mytest('x', '', ''); + mytest('x', 'x', 'x'); + mytest('aaaa', 'aaaa', 'aaaa'); + mytest('aaaaxyz', 'aaaa', 'aaaa'); + mytest('aaaaxyz', 'aaaatuv', 'aaaa'); + }); + + test('Utilities.commonFolderPrefix', () => { + function mytest(a: string, b: string, expected: string) { + assert.strictEqual(CSSPluginUtilities.commonFolderPrefix(a, b), expected, 'folderPrefix(<' + a + '>, <' + b + '>) = <' + expected + '>'); + assert.strictEqual(CSSPluginUtilities.commonFolderPrefix(b, a), expected, 'folderPrefix(<' + b + '>, <' + a + '>) = <' + expected + '>'); + } + mytest('', '', ''); + mytest('x', '', ''); + mytest('x', 'x', ''); + mytest('aaaa', 'aaaa', ''); + mytest('aaaaxyz', 'aaaa', ''); + mytest('aaaaxyz', 'aaaatuv', ''); + mytest('/', '/', '/'); + mytest('x/', '', ''); + mytest('x/', 'x/', 'x/'); + mytest('aaaa/', 'aaaa/', 'aaaa/'); + mytest('aaaa/axyz', 'aaaa/a', 'aaaa/'); + mytest('aaaa/axyz', 'aaaa/atuv', 'aaaa/'); + }); + + test('Utilities.relativePath', () => { + function mytest(a: string, b: string, expected: string) { + assert.strictEqual(CSSPluginUtilities.relativePath(a, b), expected, 'relativePath(<' + a + '>, <' + b + '>) = <' + expected + '>'); + } + mytest('', '', ''); + mytest('x', '', ''); + mytest('x', 'x', 'x'); + mytest('aaaa', 'aaaa', 'aaaa'); + mytest('aaaaxyz', 'aaaa', 'aaaa'); + mytest('aaaaxyz', 'aaaatuv', 'aaaatuv'); + + mytest('x/y/aaaaxyz', 'x/aaaatuv', '../aaaatuv'); + mytest('x/y/aaaaxyz', 'x/y/aaaatuv', 'aaaatuv'); + mytest('z/t/aaaaxyz', 'x/y/aaaatuv', '../../x/y/aaaatuv'); + mytest('aaaaxyz', 'x/y/aaaatuv', 'x/y/aaaatuv'); + + mytest('a', '/a', '/a'); + mytest('/', '/a', '/a'); + mytest('/a/b/c', '/a/b/c', '/a/b/c'); + mytest('/a/b', '/a/b/c/d', '/a/b/c/d'); + + mytest('a', 'http://a', 'http://a'); + mytest('/', 'http://a', 'http://a'); + mytest('/a/b/c', 'http://a/b/c', 'http://a/b/c'); + mytest('/a/b', 'http://a/b/c/d', 'http://a/b/c/d'); + + mytest('a', 'https://a', 'https://a'); + mytest('/', 'https://a', 'https://a'); + mytest('/a/b/c', 'https://a/b/c', 'https://a/b/c'); + mytest('/a/b', 'https://a/b/c/d', 'https://a/b/c/d'); + + mytest('x/', '', '../'); + mytest('x/', '', '../'); + mytest('x/', 'x/', ''); + mytest('x/a', 'x/a', 'a'); + }); + + test('Utilities.rewriteUrls', () => { + function mytest(originalFile: string, newFile: string, url: string, expected: string) { + assert.strictEqual(rewriteUrls(originalFile, newFile, 'sel { background:url(\'' + url + '\'); }'), 'sel { background:url(' + expected + '); }'); + assert.strictEqual(rewriteUrls(originalFile, newFile, 'sel { background:url(\"' + url + '\"); }'), 'sel { background:url(' + expected + '); }'); + assert.strictEqual(rewriteUrls(originalFile, newFile, 'sel { background:url(' + url + '); }'), 'sel { background:url(' + expected + '); }'); + } + + // img/img.png + mytest('a.css', 'b.css', 'img/img.png', 'img/img.png'); + mytest('a.css', 't/b.css', 'img/img.png', '../img/img.png'); + mytest('a.css', 'x/y/b.css', 'img/img.png', '../../img/img.png'); + mytest('x/a.css', 'b.css', 'img/img.png', 'x/img/img.png'); + mytest('x/y/a.css', 'b.css', 'img/img.png', 'x/y/img/img.png'); + mytest('x/y/a.css', 't/u/b.css', 'img/img.png', '../../x/y/img/img.png'); + mytest('x/y/a.css', 'x/u/b.css', 'img/img.png', '../y/img/img.png'); + mytest('x/y/a.css', 'x/y/b.css', 'img/img.png', 'img/img.png'); + mytest('/a.css', 'b.css', 'img/img.png', '/img/img.png'); + mytest('/a.css', 'x/b.css', 'img/img.png', '/img/img.png'); + mytest('/a.css', 'x/y/b.css', 'img/img.png', '/img/img.png'); + mytest('/x/a.css', 'b.css', 'img/img.png', '/x/img/img.png'); + mytest('/x/a.css', 'x/b.css', 'img/img.png', '/x/img/img.png'); + mytest('/x/a.css', 'x/y/b.css', 'img/img.png', '/x/img/img.png'); + mytest('/x/y/a.css', 'b.css', 'img/img.png', '/x/y/img/img.png'); + mytest('/x/y/a.css', 'x/b.css', 'img/img.png', '/x/y/img/img.png'); + mytest('/x/y/a.css', 'x/y/b.css', 'img/img.png', '/x/y/img/img.png'); + mytest('/a.css', '/b.css', 'img/img.png', '/img/img.png'); + mytest('/a.css', '/b.css', 'img/img.png', '/img/img.png'); + mytest('/x/a.css', '/b.css', 'img/img.png', '/x/img/img.png'); + mytest('/x/a.css', '/x/b.css', 'img/img.png', '/x/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'b.css', 'img/img.png', 'http://www.example.com/x/y/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'http://www.example2.com/b.css', 'img/img.png', 'http://www.example.com/x/y/img/img.png'); + mytest('https://www.example.com/x/y/a.css', 'b.css', 'img/img.png', 'https://www.example.com/x/y/img/img.png'); + + // ../img/img.png + mytest('a.css', 'b.css', '../img/img.png', '../img/img.png'); + mytest('a.css', 't/b.css', '../img/img.png', '../../img/img.png'); + mytest('a.css', 'x/y/b.css', '../img/img.png', '../../../img/img.png'); + mytest('x/a.css', 'b.css', '../img/img.png', 'img/img.png'); + mytest('x/y/a.css', 'b.css', '../img/img.png', 'x/img/img.png'); + mytest('x/y/a.css', 't/u/b.css', '../img/img.png', '../../x/img/img.png'); + mytest('x/y/a.css', 'x/u/b.css', '../img/img.png', '../img/img.png'); + mytest('x/y/a.css', 'x/y/b.css', '../img/img.png', '../img/img.png'); + mytest('/a.css', 'b.css', '../img/img.png', '/img/img.png'); + mytest('/a.css', 'x/b.css', '../img/img.png', '/img/img.png'); + mytest('/a.css', 'x/y/b.css', '../img/img.png', '/img/img.png'); + mytest('/x/a.css', 'b.css', '../img/img.png', '/img/img.png'); + mytest('/x/a.css', 'x/b.css', '../img/img.png', '/img/img.png'); + mytest('/x/a.css', 'x/y/b.css', '../img/img.png', '/img/img.png'); + mytest('/x/y/a.css', 'b.css', '../img/img.png', '/x/img/img.png'); + mytest('/x/y/a.css', 'x/b.css', '../img/img.png', '/x/img/img.png'); + mytest('/x/y/a.css', 'x/y/b.css', '../img/img.png', '/x/img/img.png'); + mytest('/a.css', '/b.css', '../img/img.png', '/img/img.png'); + mytest('/a.css', '/b.css', '../img/img.png', '/img/img.png'); + mytest('/x/a.css', '/b.css', '../img/img.png', '/img/img.png'); + mytest('/x/a.css', '/x/b.css', '../img/img.png', '/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'b.css', '../img/img.png', 'http://www.example.com/x/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'http://www.example2.com/b.css', '../img/img.png', 'http://www.example.com/x/img/img.png'); + mytest('https://www.example.com/x/y/a.css', 'b.css', '../img/img.png', 'https://www.example.com/x/img/img.png'); + + // /img/img.png + mytest('a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('a.css', 't/b.css', '/img/img.png', '/img/img.png'); + mytest('a.css', 'x/y/b.css', '/img/img.png', '/img/img.png'); + mytest('x/a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('x/y/a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('x/y/a.css', 't/u/b.css', '/img/img.png', '/img/img.png'); + mytest('x/y/a.css', 'x/u/b.css', '/img/img.png', '/img/img.png'); + mytest('x/y/a.css', 'x/y/b.css', '/img/img.png', '/img/img.png'); + mytest('/a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('/a.css', 'x/b.css', '/img/img.png', '/img/img.png'); + mytest('/a.css', 'x/y/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('/x/a.css', 'x/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/a.css', 'x/y/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/y/a.css', 'b.css', '/img/img.png', '/img/img.png'); + mytest('/x/y/a.css', 'x/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/y/a.css', 'x/y/b.css', '/img/img.png', '/img/img.png'); + mytest('/a.css', '/b.css', '/img/img.png', '/img/img.png'); + mytest('/a.css', '/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/a.css', '/b.css', '/img/img.png', '/img/img.png'); + mytest('/x/a.css', '/x/b.css', '/img/img.png', '/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'b.css', '/img/img.png', 'http://www.example.com/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'http://www.example.com/x/y/b.css', '/img/img.png', 'http://www.example.com/img/img.png'); + mytest('https://www.example.com/x/y/a.css', 'b.css', '/img/img.png', 'https://www.example.com/img/img.png'); + + // http://example.com/img/img.png + mytest('a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('a.css', 't/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('a.css', 'x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('x/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('x/y/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('x/y/a.css', 't/u/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('x/y/a.css', 'x/u/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('x/y/a.css', 'x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/a.css', 'x/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/a.css', 'x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/a.css', 'x/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/a.css', 'x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/y/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/y/a.css', 'x/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/y/a.css', 'x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/a.css', '/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/a.css', '/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/a.css', '/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('/x/a.css', '/x/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('http://www.example.com/x/y/a.css', 'http://www.example.com/x/y/b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + mytest('https://www.example.com/x/y/a.css', 'b.css', 'http://example.com/img/img.png', 'http://example.com/img/img.png'); + + + }); + + test('Utilities.rewriteUrls - quotes and spaces', () => { + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url(\'../img/img.png\'); }'), 'sel { background:url(../../x/img/img.png); }'); + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url(\t\'../img/img.png\'); }'), 'sel { background:url(../../x/img/img.png); }'); + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url( \'../img/img.png\'); }'), 'sel { background:url(../../x/img/img.png); }'); + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url(\'../img/img.png\'\t); }'), 'sel { background:url(../../x/img/img.png); }'); + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url(\'../img/img.png\' ); }'), 'sel { background:url(../../x/img/img.png); }'); + assert.strictEqual(rewriteUrls('x/y/a.css', 't/u/b.css', 'sel { background:url( \t \'../img/img.png\' \t); }'), 'sel { background:url(../../x/img/img.png); }'); + }); + + test('Bug 9601 - css should ignore data urls', () => { + const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZGM6c3ViamVjdD4KICAgICAgICAgICAgPHJkZjpCYWcvPgogICAgICAgICA8L2RjOnN1YmplY3Q+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIEltYWdlUmVhZHk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ClC8oVQAAAGnSURBVDiNrZMxTxNxGMZ///9dZWns9a4dTHSABFiuCU5dGt2d9BsQls6GD2LCd2AiQQfixKIJE0ObdKIUSvDa5uLZihP0Sh+HOw3ipOUZ3zzvL2+e932NJBaRe7/Q8Uw5eMRrzXllDU8A5mJkLB+/TflQ+67JXb+5O0FUNS9deLckns/tn2A7hxtDawZvn37Vp78AX8rmxZLDewf89HGJ+fgKCrkrBeuXKPy44hbGN7e8eTbRZwALcFE2nuOy48j6zmaTYP8Qtxaia9A1uLWQYP8QZ7OJI+s7LjsXZeMBIIlLn61xgEbLnqadtiQp7Z0orq8rrq8r7Z1IkqadtkbLnsYBuvTZkpQBhgF7SRVFJRQ3QqW9bgY5P1V6fpoDu4oboaISSqpoGLD3GzAIOEqqaFBBURHF9TWlZxlEktKzruL6mqJi5kmqaBBwJIl7Wf+7LICBIYBSKGyE+LsHuCurzPo9Zv0e7soq/u4BhY0Qpfn68p6HCbHv4Q0qtBPfarLd1LR1nAVWzDNphJq2jjXZbirxrQYV2n0PT9Lih/Rwp/xLCz3T/+gnd2VVRJs/vngAAAAASUVORK5CYII='; + + function mytest(originalFile: string, newFile: string) { + assert.strictEqual(rewriteUrls(originalFile, newFile, 'sel { background:url(' + dataUrl + '); }'), 'sel { background:url(' + dataUrl + '); }'); + assert.strictEqual(rewriteUrls(originalFile, newFile, 'sel { background:url( \t' + dataUrl + '\t ); }'), 'sel { background:url(' + dataUrl + '); }'); + } + + mytest('a.css', 'b.css'); + mytest('a.css', 't/b.css'); + mytest('a.css', 'x/y/b.css'); + mytest('x/a.css', 'b.css'); + mytest('x/y/a.css', 'b.css'); + mytest('x/y/a.css', 't/u/b.css'); + mytest('x/y/a.css', 'x/u/b.css'); + mytest('x/y/a.css', 'x/y/b.css'); + mytest('/a.css', 'b.css'); + mytest('/a.css', 'x/b.css'); + mytest('/a.css', 'x/y/b.css'); + mytest('/x/a.css', 'b.css'); + mytest('/x/a.css', 'x/b.css'); + mytest('/x/a.css', 'x/y/b.css'); + mytest('/x/y/a.css', 'b.css'); + mytest('/x/y/a.css', 'x/b.css'); + mytest('/x/y/a.css', 'x/y/b.css'); + mytest('/a.css', '/b.css'); + mytest('/a.css', '/b.css'); + mytest('/x/a.css', '/b.css'); + mytest('/x/a.css', '/x/b.css'); + mytest('http://www.example.com/x/y/a.css', 'b.css'); + mytest('http://www.example.com/x/y/a.css', 'http://www.example.com/x/y/b.css'); + mytest('https://www.example.com/x/y/a.css', 'b.css'); + }); +}); diff --git a/src/vs/base/test/node/decoder.test.ts b/src/vs/base/test/node/decoder.test.ts deleted file mode 100644 index aa2e867c741..00000000000 --- a/src/vs/base/test/node/decoder.test.ts +++ /dev/null @@ -1,22 +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 { LineDecoder } from 'vs/base/node/decoder'; - -suite('Decoder', () => { - - test('decoding', () => { - const lineDecoder = new LineDecoder(); - let res = lineDecoder.write(Buffer.from('hello')); - assert.strictEqual(res.length, 0); - - res = lineDecoder.write(Buffer.from('\nworld')); - assert.strictEqual(res[0], 'hello'); - assert.strictEqual(res.length, 1); - - assert.strictEqual(lineDecoder.end(), 'world'); - }); -}); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index e45782e236f..0f97f800ec6 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -11,9 +11,11 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { randomPath } from 'vs/base/common/extpath'; import { join, sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; -import { Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs'; +import { configureFlushOnWrite, Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs'; import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; +configureFlushOnWrite(false); // speed up all unit tests by disabling flush on write + flakySuite('PFS', function () { let testDir: string; @@ -368,24 +370,36 @@ flakySuite('PFS', function () { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(smallData, smallData, bigData, bigData); + return testWriteFile(smallData, smallData, bigData, bigData); + }); + + test('writeFile (string) - flush on write', async () => { + configureFlushOnWrite(true); + try { + const smallData = 'Hello World'; + const bigData = (new Array(100 * 1024)).join('Large String\n'); + + return await testWriteFile(smallData, smallData, bigData, bigData); + } finally { + configureFlushOnWrite(false); + } }); test('writeFile (Buffer)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData); + return testWriteFile(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData); }); test('writeFile (UInt8Array)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); - return testWriteFileAndFlush(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData); + return testWriteFile(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData); }); - async function testWriteFileAndFlush( + async function testWriteFile( smallData: string | Buffer | Uint8Array, smallDataValue: string, bigData: string | Buffer | Uint8Array, diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 42e68ffea41..9b4656b31c4 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -17,7 +17,8 @@ // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor const fnArgs = args.slice(0, -1).join(','); const fnBody = args.pop()!.toString(); - const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`; + // Do not add a new line to fnBody, as this will confuse source maps. + const body = `(function anonymous(${fnArgs}) { ${fnBody}\n})`; return body; } }) @@ -76,14 +77,18 @@ }); } - const loadCode = function (moduleId: string) { + function configureAMDLoader() { + require.config({ + baseUrl: monacoBaseUrl, + catchError: true, + trustedTypesPolicy, + amdModulesPattern: /^vs\// + }); + } + + function loadCode(moduleId: string) { loadAMDLoader().then(() => { - require.config({ - baseUrl: monacoBaseUrl, - catchError: true, - trustedTypesPolicy, - amdModulesPattern: /^vs\// - }); + configureAMDLoader(); require([moduleId], function (ws) { setTimeout(function () { const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { @@ -97,7 +102,14 @@ }, 0); }); }); - }; + } + + // If the loader is already defined, configure it immediately + // This helps in the bundled case, where we must load nls files + // and they need a correct baseUrl to be loaded. + if (typeof (self).define === 'function' && (self).define.amd) { + configureAMDLoader(); + } let isFirstMessage = true; const beforeReadyMessages: MessageEvent[] = []; diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 4c5c3eb50b3..2e02cddf363 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -49,7 +49,7 @@ availableLanguages: { '*': locale }, - baseUrl: '{{WORKBENCH_NLS_BASE_URL}}' + translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}' }; } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts index f26355d5692..3de8633f588 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts @@ -3,27 +3,186 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, ServerDidUninstallExtensionEvent, ServerInstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { DidChangeProfilesEvent, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + +const uninstalOptions: UninstallOptions = { versionOnly: true, donotIncludePack: true, donotCheckDependents: true }; export class ExtensionsCleaner extends Disposable { constructor( - @IExtensionManagementService extensionManagementService: ExtensionManagementService, + @INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @IExtensionStorageService extensionStorageService: IExtensionStorageService, @IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService, + @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @ILogService logService: ILogService, ) { super(); - extensionManagementService.removeDeprecatedExtensions(); + + extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1); migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService); ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); + this._register(instantiationService.createInstance(ProfileExtensionsCleaner)); } + +} + +class ProfileExtensionsCleaner extends Disposable { + + private profileExtensionsLocations = new Map; + + private readonly profileModeDisposables = this._register(new MutableDisposable()); + + constructor( + @INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); + } + + private async onDidChangeProfiles({ added, removed, all }: Omit): Promise { + try { + await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); + } catch (error) { + this.logService.error(error); + } + + if (all.length === 1) { + // Exit profile mode + this.profileModeDisposables.clear(); + // Listen for entering into profile mode + const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => { + disposable.dispose(); + this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); + })); + return; + } + + try { + if (added.length) { + await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); + // Enter profile mode + if (!this.profileModeDisposables.value) { + this.profileModeDisposables.value = new DisposableStore(); + this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear())); + this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); + this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); + this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); + await this.uninstallExtensionsNotInProfiles(); + } + } + } catch (error) { + this.logService.error(error); + } + } + + private async uninstallExtensionsNotInProfiles(): Promise { + const installed = await this.extensionManagementService.getAllUserInstalled(); + const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); + if (toUninstall.length) { + await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); + } + } + + private async onDidInstallExtensions(installedExtensions: readonly ServerInstallExtensionResult[]): Promise { + for (const { local, profileLocation } of installedExtensions) { + if (!local || !profileLocation) { + continue; + } + this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation); + } + } + + private async onDidUninstallExtension(e: ServerDidUninstallExtensionEvent): Promise { + if (!e.profileLocation || !e.version) { + return; + } + if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) { + await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]); + } + } + + private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise { + const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation); + for (const extension of extensions) { + this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation); + } + } + + private async removeExtensionsFromProfile(removedProfile: URI): Promise { + const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = []; + for (const key of [...this.profileExtensionsLocations.keys()]) { + if (!this.removeExtensionWithKey(key, removedProfile)) { + continue; + } + const extensionToRemove = this.fromKey(key); + if (extensionToRemove) { + extensionsToRemove.push(extensionToRemove); + } + } + if (extensionsToRemove.length) { + await this.uninstallExtensions(extensionsToRemove); + } + } + + private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void { + let locations = this.profileExtensionsLocations.get(key); + if (!locations) { + locations = []; + this.profileExtensionsLocations.set(key, locations); + } + locations.push(extensionsProfileLocation); + } + + private removeExtensionWithKey(key: string, profileLocation: URI): boolean { + const profiles = this.profileExtensionsLocations.get(key); + if (profiles) { + const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation)); + if (index > -1) { + profiles.splice(index, 1); + } + } + if (!profiles?.length) { + this.profileExtensionsLocations.delete(key); + return true; + } + return false; + } + + private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise { + const installed = await this.extensionManagementService.getAllUserInstalled(); + const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version))); + if (toUninstall.length) { + await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); + } + } + + private getKey(identifier: IExtensionIdentifier, version: string): string { + return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`; + } + + private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined { + const [id, version] = getIdAndVersion(key); + return version ? { identifier: { id }, version } : undefined; + } + } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 10afe24a571..84e0e787a39 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -34,7 +34,7 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/ import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { IFileService } from 'vs/platform/files/common/files'; @@ -53,7 +53,6 @@ import { FollowerLogService, LoggerChannelClient, LogLevelChannelClient } from ' import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; -import { RequestService } from 'vs/platform/request/browser/requestService'; import { IRequestService } from 'vs/platform/request/common/request'; import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -63,8 +62,7 @@ import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; @@ -100,10 +98,13 @@ import { InspectProfilingService as V8InspectProfilingService } from 'vs/platfor import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profiling'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; +import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; +import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -// import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; class SharedProcessMain extends Disposable { @@ -131,7 +132,7 @@ class SharedProcessMain extends Disposable { // application is shutting down anyways. // const eventName = 'vscode:electron-main->shared-process=disposeWorker'; - const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => this.onDisposeWorker(configuration); + const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); }; ipcRenderer.on(eventName, onDisposeWorker); this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker))); } @@ -229,12 +230,16 @@ class SharedProcessMain extends Disposable { )); fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); + // User Data Profiles + const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService)); + services.set(IUserDataProfilesService, userDataProfilesService); + // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); + const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); // Storage (global access only) - const storageService = new NativeStorageService(undefined, mainProcessService, environmentService); + const storageService = new NativeStorageService(undefined, { defaultProfile: userDataProfilesService.defaultProfile, currentProfile: userDataProfilesService.defaultProfile }, mainProcessService, environmentService); services.set(IStorageService, storageService); this._register(toDisposable(() => storageService.flush())); @@ -248,7 +253,7 @@ class SharedProcessMain extends Disposable { services.set(IUriIdentityService, new UriIdentityService(fileService)); // Request - services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, logService)); // Checksum services.set(IChecksumService, new SyncDescriptor(ChecksumService)); @@ -271,25 +276,20 @@ class SharedProcessMain extends Disposable { // Telemetry let telemetryService: ITelemetryService; const appenders: ITelemetryAppender[] = []; + const internalTelemetry = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { const logAppender = new TelemetryLogAppender(loggerService, environmentService); appenders.push(logAppender); const { installSourcePath } = environmentService; - const internalTesting = configurationService.getValue('telemetry.internalTesting'); - if (internalTesting && productService.aiConfig?.ariaKey) { - const collectorAppender = new OneDataSystemAppender('monacoworkbench', null, productService.aiConfig.ariaKey); + if (productService.aiConfig?.ariaKey) { + const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey); this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); - } else if (productService.aiConfig && productService.aiConfig.asimovKey) { - // Application Insights - const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey); - this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data - appenders.push(appInsightsAppender); } telemetryService = new TelemetryService({ appenders, - commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry, installSourcePath), sendErrorTelemetry: true, piiPaths: getPiiPathsFromEnvironment(environmentService), }, configurationService, productService); @@ -307,8 +307,9 @@ class SharedProcessMain extends Disposable { services.set(ICustomEndpointTelemetryService, customEndpointTelemetryService); // Extension Management + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); // Extension Gallery services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); @@ -419,6 +420,7 @@ class SharedProcessMain extends Disposable { // Worker const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService)); this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel); + } private registerErrorHandler(logService: ILogService): void { diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html deleted file mode 100644 index 47066f520be..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js deleted file mode 100644 index 7ceef489fcc..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ /dev/null @@ -1,213 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// - -//@ts-check -(function () { - 'use strict'; - - const bootstrapWindow = bootstrapWindowLib(); - - // Add a perf entry right from the top - performance.mark('code/didStartRenderer'); - - // Load workbench main JS, CSS and NLS all in parallel. This is an - // optimization to prevent a waterfall of loading to happen, because - // we know for a fact that workbench.desktop.main will depend on - // the related CSS and NLS counterparts. - bootstrapWindow.load([ - 'vs/workbench/workbench.desktop.main', - 'vs/nls!vs/workbench/workbench.desktop.main', - 'vs/css!vs/workbench/workbench.desktop.main' - ], - function (_, configuration) { - - // Mark start of workbench - performance.mark('code/didLoadWorkbenchMain'); - - // @ts-ignore - return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); - }, - { - configureDeveloperSettings: function (windowConfig) { - return { - // disable automated devtools opening on error when running extension tests - // as this can lead to nondeterministic test execution (devtools steals focus) - forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', - // enable devtools keybindings in extension development window - forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, - removeDeveloperKeybindingsAfterLoad: true - }; - }, - canModifyDOM: function (windowConfig) { - showSplash(windowConfig); - }, - beforeLoaderConfig: function (loaderConfig) { - loaderConfig.recordStats = true; - }, - beforeRequire: function () { - performance.mark('code/willLoadWorkbenchMain'); - - // It looks like browsers only lazily enable - // the element when needed. Since we - // leverage canvas elements in our code in many - // locations, we try to help the browser to - // initialize canvas when it is idle, right - // before we wait for the scripts to be loaded. - // @ts-ignore - window.requestIdleCallback(() => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - canvas.remove(); - }, { timeout: 50 }); - } - } - ); - - //#region Helpers - - /** - * @typedef {import('../../../platform/window/common/window').INativeWindowConfiguration} INativeWindowConfiguration - * @typedef {import('../../../platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs - * - * @returns {{ - * load: ( - * modules: string[], - * resultCallback: (result, configuration: INativeWindowConfiguration & NativeParsedArgs) => unknown, - * options?: { - * configureDeveloperSettings?: (config: INativeWindowConfiguration & NativeParsedArgs) => { - * forceDisableShowDevtoolsOnError?: boolean, - * forceEnableDeveloperKeybindings?: boolean, - * disallowReloadKeybinding?: boolean, - * removeDeveloperKeybindingsAfterLoad?: boolean - * }, - * canModifyDOM?: (config: INativeWindowConfiguration & NativeParsedArgs) => void, - * beforeLoaderConfig?: (loaderConfig: object) => void, - * beforeRequire?: () => void - * } - * ) => Promise - * }} - */ - function bootstrapWindowLib() { - // @ts-ignore (defined in bootstrap-window.js) - return window.MonacoBootstrapWindow; - } - - /** - * @param {INativeWindowConfiguration & NativeParsedArgs} configuration - */ - function showSplash(configuration) { - performance.mark('code/willShowPartsSplash'); - - let data = configuration.partsSplash; - - if (data) { - // high contrast mode has been turned by the OS -> ignore stored colors and layouts - if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) { - if ((configuration.colorScheme.dark && data.baseTheme !== 'hc-black') || (!configuration.colorScheme.dark && data.baseTheme !== 'hc-light')) { - data = undefined; - } - } else if (configuration.autoDetectColorScheme) { - // OS color scheme is tracked and has changed - if ((configuration.colorScheme.dark && data.baseTheme !== 'vs-dark') || (!configuration.colorScheme.dark && data.baseTheme !== 'vs')) { - data = undefined; - } - } - } - - // developing an extension -> ignore stored layouts - if (data && configuration.extensionDevelopmentPath) { - data.layoutInfo = undefined; - } - - // minimal color configuration (works with or without persisted data) - let baseTheme, shellBackground, shellForeground; - if (data) { - baseTheme = data.baseTheme; - shellBackground = data.colorInfo.editorBackground; - shellForeground = data.colorInfo.foreground; - } else if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) { - if (configuration.colorScheme.dark) { - baseTheme = 'hc-black'; - shellBackground = '#000000'; - shellForeground = '#FFFFFF'; - } else { - baseTheme = 'hc-light'; - shellBackground = '#FFFFFF'; - shellForeground = '#000000'; - } - } else if (configuration.autoDetectColorScheme) { - if (configuration.colorScheme.dark) { - baseTheme = 'vs-dark'; - shellBackground = '#1E1E1E'; - shellForeground = '#CCCCCC'; - } else { - baseTheme = 'vs'; - shellBackground = '#FFFFFF'; - shellForeground = '#000000'; - } - } - - const style = document.createElement('style'); - style.className = 'initialShellColors'; - document.head.appendChild(style); - style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; - - // restore parts if possible (we might not always store layout info) - if (data?.layoutInfo) { - const { layoutInfo, colorInfo } = data; - - const splash = document.createElement('div'); - splash.id = 'monaco-parts-splash'; - splash.className = baseTheme; - - if (layoutInfo.windowBorder) { - splash.style.position = 'relative'; - splash.style.height = 'calc(100vh - 2px)'; - splash.style.width = 'calc(100vw - 2px)'; - splash.style.border = '1px solid var(--window-border-color)'; - splash.style.setProperty('--window-border-color', colorInfo.windowBorder); - - if (layoutInfo.windowBorderRadius) { - splash.style.borderRadius = layoutInfo.windowBorderRadius; - } - } - - // ensure there is enough space - layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth)); - - // part: title - const titleDiv = document.createElement('div'); - titleDiv.setAttribute('style', `position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;`); - splash.appendChild(titleDiv); - - // part: activity bar - const activityDiv = document.createElement('div'); - activityDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};`); - splash.appendChild(activityDiv); - - // part: side bar (only when opening workspace/folder) - // folder or workspace -> status bar color, sidebar - if (configuration.workspace) { - const sideDiv = document.createElement('div'); - sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`); - splash.appendChild(sideDiv); - } - - // part: statusbar - const statusDiv = document.createElement('div'); - statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`); - splash.appendChild(statusDiv); - - document.body.appendChild(splash); - } - - performance.mark('code/didShowPartsSplash'); - } - - //#endregion -}()); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index eb752810e1c..d7f60dc688b 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -70,12 +70,12 @@ import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedPro import { ISignService } from 'vs/platform/sign/common/sign'; import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc'; -import { GlobalStorageMainService, IGlobalStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; +import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, machineIdKey, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { getPiiPathsFromEnvironment, getTelemetryLevel, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getPiiPathsFromEnvironment, getTelemetryLevel, isInternalTelemetry, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/common/updateIpc'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; @@ -101,6 +101,13 @@ import { IWorkspacesManagementMainService, WorkspacesManagementMainService } fro import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-main/credentialsMainService'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { PolicyChannel } from 'vs/platform/policy/common/policyIpc'; +import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; +import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit'; +import { RequestChannel } from 'vs/platform/request/common/requestIpc'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; +import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; /** * The main VS Code application. There will only ever be one instance, @@ -121,7 +128,8 @@ export class CodeApplication extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @IStateMainService private readonly stateMainService: IStateMainService, @IFileService private readonly fileService: IFileService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, ) { super(); @@ -524,8 +532,14 @@ export class CodeApplication extends Disposable { // Services const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady); - // Setup Auth Handler - this._register(appInstantiationService.createInstance(ProxyAuthHandler)); + // Setup Handlers + this.setUpHandlers(appInstantiationService); + + // Ensure profile exists when passed in from CLI + const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(this.environmentMainService.args); + if (profilePromise) { + await profilePromise; + } // Init Channels appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); @@ -542,6 +556,14 @@ export class CodeApplication extends Disposable { } } + private setUpHandlers(instantiationService: IInstantiationService): void { + // Auth Handler + this._register(instantiationService.createInstance(ProxyAuthHandler)); + + // Default Extensions Profile Init Handler + this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler)); + } + private async resolveMachineId(): Promise { // We cache the machineId for faster lookups on startup @@ -647,7 +669,7 @@ export class CodeApplication extends Disposable { // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); - services.set(IGlobalStorageMainService, new SyncDescriptor(GlobalStorageMainService)); + services.set(IApplicationStorageMainService, new SyncDescriptor(ApplicationStorageMainService)); // External terminal if (isWindows) { @@ -667,9 +689,10 @@ export class CodeApplication extends Disposable { // Telemetry if (supportsTelemetry(this.productService, this.environmentMainService)) { + const isInternal = isInternalTelemetry(this.productService, this.configurationService); const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath); + const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal, this.environmentMainService.installSourcePath); const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService); const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true }; @@ -678,6 +701,10 @@ export class CodeApplication extends Disposable { services.set(ITelemetryService, NullTelemetryService); } + // Default Extensions Profile Init + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); + services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); + // Init services that require it await backupMainService.initialize(); @@ -709,6 +736,15 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel); sharedProcessClient.then(client => client.registerChannel(LOCAL_FILE_SYSTEM_CHANNEL_NAME, fileSystemProviderChannel)); + // User Data Profiles + const userDataProfilesService = ProxyChannel.fromService(accessor.get(IUserDataProfilesMainService)); + mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService); + sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService)); + + // Request + const requestService = new RequestChannel(accessor.get(IRequestService)); + sharedProcessClient.then(client => client.registerChannel('request', requestService)); + // Update const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); mainProcessElectronServer.registerChannel('update', updateChannel); @@ -980,6 +1016,7 @@ export class CodeApplication extends Disposable { cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, + mergeMode: args.merge, noRecentEntry, waitMarkerFileURI, gotoLineMode: args.goto, @@ -1148,7 +1185,7 @@ export class CodeApplication extends Disposable { // Initialize update service const updateService = accessor.get(IUpdateService); if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { - updateService.initialize(); + await updateService.initialize(); } // Start to fetch shell environment (if needed) after window has opened diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index cfa944e76c8..1a4f72bd620 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -62,10 +62,14 @@ import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; +import { IUserDataProfilesMainService, UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; /** * The main VS Code entry point. @@ -93,13 +97,13 @@ class CodeMain { setUnexpectedErrorHandler(err => console.error(err)); // Create services - const [instantiationService, instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService] = this.createServices(); + const [instantiationService, instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService, userDataProfilesMainService] = this.createServices(); try { // Init services try { - await this.initServices(environmentMainService, policyService, configurationService, stateMainService); + await this.initServices(environmentMainService, userDataProfilesMainService, configurationService, stateMainService); } catch (error) { // Show a dialog for errors that can be resolved by the user @@ -141,7 +145,7 @@ class CodeMain { } } - private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, IPolicyService, ConfigurationService, StateMainService, BufferLogService, IProductService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateMainService, BufferLogService, IProductService, UserDataProfilesMainService] { const services = new ServiceCollection(); const disposables = new DisposableStore(); process.once('exit', () => disposables.dispose()); @@ -168,9 +172,21 @@ class CodeMain { const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // URI Identity + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + // Logger services.set(ILoggerService, new LoggerService(logService, fileService)); + // State + const stateMainService = new StateMainService(environmentMainService, logService, fileService); + services.set(IStateMainService, stateMainService); + + // User Data Profiles + const userDataProfilesMainService = new UserDataProfilesMainService(stateMainService, uriIdentityService, environmentMainService, fileService, logService); + services.set(IUserDataProfilesMainService, userDataProfilesMainService); + // Policy const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(productService.win32RegValueName)) : environmentMainService.policyFile ? disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)) @@ -178,16 +194,12 @@ class CodeMain { services.set(IPolicyService, policyService); // Configuration - const configurationService = new ConfigurationService(environmentMainService.settingsResource, fileService, policyService, logService); + const configurationService = new ConfigurationService(userDataProfilesMainService.defaultProfile.settingsResource, fileService, policyService, logService); services.set(IConfigurationService, configurationService); // Lifecycle services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); - // State - const stateMainService = new StateMainService(environmentMainService, logService, fileService); - services.set(IStateMainService, stateMainService); - // Request services.set(IRequestService, new SyncDescriptor(RequestMainService)); @@ -203,7 +215,7 @@ class CodeMain { // Protocol services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, policyService, configurationService, stateMainService, bufferLogService, productService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService, userDataProfilesMainService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -223,26 +235,28 @@ class CodeMain { return instanceEnvironment; } - private initServices(environmentMainService: IEnvironmentMainService, policyService: IPolicyService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { - return Promises.settled([ + private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { + await Promises.settled([ // Environment service (paths) Promise.all([ environmentMainService.extensionsPath, environmentMainService.codeCachePath, environmentMainService.logsPath, - environmentMainService.globalStorageHome.fsPath, + userDataProfilesMainService.defaultProfile.globalStorageHome.fsPath, environmentMainService.workspaceStorageHome.fsPath, environmentMainService.localHistoryHome.fsPath, environmentMainService.backupHome ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), - // Configuration service - configurationService.initialize(), - // State service - stateMainService.init() + stateMainService.init(), + + // Configuration service + configurationService.initialize() ]); + + userDataProfilesMainService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); } private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index ff591f90b72..ed78a7d1963 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -73,6 +73,7 @@ export class IssueReporter extends Disposable { const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined; this.issueReporterModel = new IssueReporterModel({ + ...configuration.data, issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, @@ -80,7 +81,7 @@ export class IssueReporter extends Disposable { }, extensionsDisabled: !!configuration.disableExtensions, fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, - selectedExtension: targetExtension, + selectedExtension: targetExtension }); const issueReporterElement = this.getElementById('issue-reporter'); @@ -140,6 +141,7 @@ export class IssueReporter extends Disposable { this.handleExtensionData(configuration.data.enabledExtensions); this.updateExperimentsInfo(configuration.data.experiments); this.updateRestrictedMode(configuration.data.restrictedMode); + this.updateUnsupportedMode(configuration.data.isUnsupported); } render(): void { @@ -150,14 +152,10 @@ export class IssueReporter extends Disposable { const { fileOnExtension } = this.issueReporterModel.getData(); if (fileOnExtension) { const issueTitle = document.getElementById('issue-title'); - if (issueTitle) { - issueTitle.focus(); - } + issueTitle?.focus(); } else { const issueType = document.getElementById('issue-type'); - if (issueType) { - issueType.focus(); - } + issueType?.focus(); } } @@ -729,7 +727,7 @@ export class IssueReporter extends Disposable { } else if (!fileOnMarketplace) { show(extensionsBlock); } - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } else if (issueType === IssueType.PerformanceIssue) { show(problemSource); @@ -748,10 +746,10 @@ export class IssueReporter extends Disposable { show(extensionsBlock); } - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } else if (issueType === IssueType.FeatureRequest) { - reset(descriptionTitle, localize('description', "Description"), $('span.required-input', undefined, '*')); + reset(descriptionTitle, localize('description', "Description") + ' ', $('span.required-input', undefined, '*')); reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); show(problemSource); @@ -1154,6 +1152,10 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ restrictedMode }); } + private updateUnsupportedMode(isUnsupported: boolean) { + this.issueReporterModel.update({ isUnsupported }); + } + private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); const target = document.querySelector('.block-experiments .block-info'); @@ -1197,21 +1199,15 @@ export class IssueReporter extends Disposable { private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void { const element = this.getElementById(elementId); - if (element) { - element.addEventListener(eventType, handler); - } + element?.addEventListener(eventType, handler); } } // helper functions function hide(el: Element | undefined | null) { - if (el) { - el.classList.add('hidden'); - } + el?.classList.add('hidden'); } function show(el: Element | undefined | null) { - if (el) { - el.classList.remove('hidden'); - } + el?.classList.remove('hidden'); } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 37a1a40beb0..a58acca2f5a 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -33,6 +33,8 @@ export interface IssueReporterData { filterResultCount?: number; experimentInfo?: string; restrictedMode?: boolean; + isUnsupported?: boolean; + isSandboxed?: boolean; } export class IssueReporterModel { @@ -61,14 +63,22 @@ export class IssueReporterModel { } serialize(): string { + const modes = []; + if (this._data.restrictedMode) { + modes.push('Restricted'); + } + if (this._data.isUnsupported) { + modes.push('Unsupported'); + } return ` -Issue Type: ${this.getIssueTypeTitle()} +Type: ${this.getIssueTypeTitle()} ${this._data.issueDescription} ${this.getExtensionVersion()} VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion} OS version: ${this._data.versionInfo && this._data.versionInfo.os} -Restricted Mode: ${this._data.restrictedMode ? 'Yes' : 'No'} +Modes:${modes.length ? ' ' + modes.join(', ') : ''} +Sandboxed: ${this._data.isSandboxed ? 'Yes' : 'No'} ${this.getRemoteOSes()} ${this.getInfos()} `; diff --git a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts index 99e6cc82420..6467d66bd36 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts @@ -11,11 +11,23 @@ const sendProcessInfoLabel = escape(localize('sendProcessInfo', "Include my curr const sendWorkspaceInfoLabel = escape(localize('sendWorkspaceInfo', "Include my workspace metadata")); const sendExtensionsLabel = escape(localize('sendExtensions', "Include my enabled extensions")); const sendExperimentsLabel = escape(localize('sendExperiments', "Include A/B experiment info")); +const reviewGuidanceLabel = localize( // intentionally not escaped because of its embedded tags + { + key: 'reviewGuidanceLabel', + comment: [ + '{Locked=""}', + '{Locked=""}' + ] + }, + 'Before you report an issue here please review the guidance we provide.' +); export default (): string => `
+
${reviewGuidanceLabel}
+
@@ -25,7 +37,7 @@ export default (): string => `
- + diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 8e756e829d9..2fcf59e3ae0 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -257,6 +257,7 @@ class ProcessExplorer { await this.createProcessTree(processRoots); } else { this.tree.setInput({ processes: { processRoots } }); + this.tree.layout(window.innerHeight, window.innerWidth); } this.requestProcessList(0); @@ -342,6 +343,13 @@ class ProcessExplorer { this.showContextMenu(e.element, true); } }); + + container.style.height = `${window.innerHeight}px`; + + window.addEventListener('resize', () => { + container.style.height = `${window.innerHeight}px`; + this.tree?.layout(window.innerHeight, window.innerWidth); + }); } private isDebuggable(cmd: string): boolean { @@ -416,6 +424,47 @@ class ProcessExplorer { content.push(`.monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } + // Scrollbars + if (styles.scrollbarShadowColor) { + content.push(` + .monaco-scrollable-element > .shadow.top { + box-shadow: ${styles.scrollbarShadowColor} 0 6px 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.left { + box-shadow: ${styles.scrollbarShadowColor} 6px 0 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.top.left { + box-shadow: ${styles.scrollbarShadowColor} 6px 6px 6px -6px inset; + } + `); + } + + if (styles.scrollbarSliderBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider { + background: ${styles.scrollbarSliderBackgroundColor}; + } + `); + } + + if (styles.scrollbarSliderHoverBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider:hover { + background: ${styles.scrollbarSliderHoverBackgroundColor}; + } + `); + } + + if (styles.scrollbarSliderActiveBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider.active { + background: ${styles.scrollbarSliderActiveBackgroundColor}; + } + `); + } + styleElement.textContent = content.join('\n'); if (styles.color) { diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index 47066f520be..16e089df650 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index e271fdf1bf4..c8e4431d6db 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -16,10 +16,10 @@ // Load workbench main JS, CSS and NLS all in parallel. This is an // optimization to prevent a waterfall of loading to happen, because - // we know for a fact that workbench.desktop.sandbox.main will depend on + // we know for a fact that workbench.desktop.main will depend on // the related CSS and NLS counterparts. bootstrapWindow.load([ - 'vs/workbench/workbench.desktop.sandbox.main', + 'vs/workbench/workbench.desktop.main', 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], @@ -36,7 +36,7 @@ return { // disable automated devtools opening on error when running extension tests // as this can lead to nondeterministic test execution (devtools steals focus) - forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', + forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string' || windowConfig['enable-smoke-test-driver'] === true, // enable devtools keybindings in extension development window forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, removeDeveloperKeybindingsAfterLoad: true @@ -61,7 +61,7 @@ window.requestIdleCallback(() => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); + context?.clearRect(0, 0, canvas.width, canvas.height); canvas.remove(); }, { timeout: 50 }); } diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 80e5dec517d..190d64038e9 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -8,7 +8,7 @@ import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync import { homedir, release, tmpdir } from 'os'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { Event } from 'vs/base/common/event'; -import { isAbsolute, resolve } from 'vs/base/common/path'; +import { isAbsolute, resolve, join } from 'vs/base/common/path'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; import { randomPort } from 'vs/base/common/ports'; import { isString } from 'vs/base/common/types'; @@ -24,6 +24,8 @@ import product from 'vs/platform/product/common/product'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { randomPath } from 'vs/base/common/extpath'; import { Utils } from 'vs/platform/profiling/common/profiling'; +import { dirname } from 'vs/base/common/resources'; +import { FileAccess } from 'vs/base/common/network'; function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] @@ -59,6 +61,23 @@ export async function main(argv: string[]): Promise { console.log(buildVersionMessage(product.version, product.commit)); } + // Shell integration + else if (args['locate-shell-integration-path']) { + let file: string; + switch (args['locate-shell-integration-path']) { + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path bash)"` + case 'bash': file = 'shellIntegration-bash.sh'; break; + // Usage: `if ($env:TERM_PROGRAM -eq "vscode") { . "$(code --locate-shell-integration-path pwsh)" }` + case 'pwsh': file = 'shellIntegration.ps1'; break; + // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` + case 'zsh': file = 'shellIntegration-rc.zsh'; break; + // Usage: `string match -q "$TERM_PROGRAM" "vscode"; and . (code --locate-shell-integration-path fish)` + case 'fish': file = 'shellIntegration.fish'; break; + default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); + } + console.log(join(dirname(FileAccess.asFileUri('', require)).fsPath, 'out', 'vs', 'workbench', 'contrib', 'terminal', 'browser', 'media', file)); + } + // Extensions Management else if (shouldSpawnCliProcess(args)) { const cli = await new Promise((resolve, reject) => require(['vs/code/node/cliProcessMain'], resolve, reject)); @@ -162,7 +181,7 @@ export async function main(argv: string[]): Promise { // returns a file path where stdin input is written into (write in progress). try { - readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written + await readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written // Make sure to open tmp file addArg(argv, stdinFilePath); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 32d5fb9c6a3..eda61ad1689 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -13,7 +13,6 @@ import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; -import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Promises } from 'vs/base/node/pfs'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -24,10 +23,11 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; +import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -47,14 +47,18 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; +import { IStateService } from 'vs/platform/state/node/state'; +import { StateService } from 'vs/platform/state/node/stateService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; +import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; class CliMain extends Disposable { @@ -100,7 +104,7 @@ class CliMain extends Disposable { }); } - private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> { + private async initServices(): Promise<[IInstantiationService, OneDataSystemAppender[]]> { const services = new ServiceCollection(); // Product @@ -132,6 +136,18 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // State + const stateService = new StateService(environmentService, logService, fileService); + services.set(IStateService, stateService); + + // Uri Identity + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + + // User Data Profiles + const userDataProfilesService = new UserDataProfilesService(stateService, uriIdentityService, environmentService, fileService, logService); + services.set(IUserDataProfilesService, userDataProfilesService); + // Policy const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(productService.win32RegValueName)) : environmentService.policyFile ? this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)) @@ -139,11 +155,16 @@ class CliMain extends Disposable { services.set(IPolicyService, policyService); // Configuration - const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService, policyService, logService)); + const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); - // Init config - await configurationService.initialize(); + // Initialize + await Promise.all([ + stateService.init(), + configurationService.initialize() + ]); + + userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); @@ -155,8 +176,9 @@ class CliMain extends Disposable { services.set(IDownloadService, new SyncDescriptor(DownloadService)); // Extensions + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); @@ -164,10 +186,11 @@ class CliMain extends Disposable { services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); // Telemetry - const appenders: AppInsightsAppender[] = []; + const appenders: OneDataSystemAppender[] = []; + const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.asimovKey) { - appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey)); + if (productService.aiConfig && productService.aiConfig.ariaKey) { + appenders.push(new OneDataSystemAppender(isInternal, 'monacoworkbench', null, productService.aiConfig.ariaKey)); } const { installSourcePath } = environmentService; @@ -178,7 +201,7 @@ class CliMain extends Disposable { commonProperties: (async () => { let machineId: string | undefined = undefined; try { - const storageContents = await Promises.readFile(joinPath(environmentService.globalStorageHome, 'storage.json').fsPath); + const storageContents = await Promises.readFile(environmentService.stateResource.fsPath); machineId = JSON.parse(storageContents.toString())[machineIdKey]; } catch (error) { if (error.code !== 'ENOENT') { @@ -186,7 +209,7 @@ class CliMain extends Disposable { } } - return resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, machineId, productService.msftInternalDomains, installSourcePath); + return resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal, installSourcePath); })(), piiPaths: getPiiPathsFromEnvironment(environmentService) }; diff --git a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts index 87bca75fedf..aa0fac7bf0d 100644 --- a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts +++ b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts @@ -27,13 +27,14 @@ suite('IssueReporter', () => { const issueReporterModel = new IssueReporterModel({}); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No Extensions: none `); @@ -58,13 +59,14 @@ Extensions: none }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No
System Info @@ -102,13 +104,14 @@ Restricted Mode: No }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No
System Info @@ -157,13 +160,14 @@ vsins829:30139715 }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No
System Info @@ -214,13 +218,14 @@ Restricted Mode: No }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No Remote OS version: Linux x64 4.18.0
@@ -263,13 +268,14 @@ Remote OS version: Linux x64 4.18.0 }); assert.strictEqual(issueReporterModel.serialize(), ` -Issue Type: Bug +Type: Bug undefined VS Code version: undefined OS version: undefined -Restricted Mode: No +Modes: +Sandboxed: No
System Info @@ -287,6 +293,25 @@ Restricted Mode: No `); }); + test('should supply mode if applicable', () => { + const issueReporterModel = new IssueReporterModel({ + isUnsupported: true, + restrictedMode: true + }); + assert.strictEqual(issueReporterModel.serialize(), + ` +Type: Bug + +undefined + +VS Code version: undefined +OS version: undefined +Modes: Restricted, Unsupported +Sandboxed: No + +Extensions: none +`); + }); test('should normalize GitHub urls', () => { [ 'https://github.com/repo', diff --git a/src/vs/css.build.js b/src/vs/css.build.js deleted file mode 100644 index c8875859684..00000000000 --- a/src/vs/css.build.js +++ /dev/null @@ -1,359 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/*--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - * Please make sure to make edits in the .ts file at https://github.com/microsoft/vscode-loader/ - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------*/ -'use strict'; -var _cssPluginGlobal = this; -var CSSBuildLoaderPlugin; -(function (CSSBuildLoaderPlugin) { - var global = (_cssPluginGlobal || {}); - var BrowserCSSLoader = /** @class */ (function () { - function BrowserCSSLoader() { - this._pendingLoads = 0; - } - BrowserCSSLoader.prototype.attachListeners = function (name, linkNode, callback, errorback) { - var unbind = function () { - linkNode.removeEventListener('load', loadEventListener); - linkNode.removeEventListener('error', errorEventListener); - }; - var loadEventListener = function (e) { - unbind(); - callback(); - }; - var errorEventListener = function (e) { - unbind(); - errorback(e); - }; - linkNode.addEventListener('load', loadEventListener); - linkNode.addEventListener('error', errorEventListener); - }; - BrowserCSSLoader.prototype._onLoad = function (name, callback) { - this._pendingLoads--; - callback(); - }; - BrowserCSSLoader.prototype._onLoadError = function (name, errorback, err) { - this._pendingLoads--; - errorback(err); - }; - BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { - this._pendingLoads++; - var head = document.head || document.getElementsByTagName('head')[0]; - var other = head.getElementsByTagName('link') || head.getElementsByTagName('script'); - if (other.length > 0) { - head.insertBefore(linkNode, other[other.length - 1]); - } - else { - head.appendChild(linkNode); - } - }; - BrowserCSSLoader.prototype.createLinkTag = function (name, cssUrl, externalCallback, externalErrorback) { - var _this = this; - var linkNode = document.createElement('link'); - linkNode.setAttribute('rel', 'stylesheet'); - linkNode.setAttribute('type', 'text/css'); - linkNode.setAttribute('data-name', name); - var callback = function () { return _this._onLoad(name, externalCallback); }; - var errorback = function (err) { return _this._onLoadError(name, externalErrorback, err); }; - this.attachListeners(name, linkNode, callback, errorback); - linkNode.setAttribute('href', cssUrl); - return linkNode; - }; - BrowserCSSLoader.prototype._linkTagExists = function (name, cssUrl) { - var i, len, nameAttr, hrefAttr, links = document.getElementsByTagName('link'); - for (i = 0, len = links.length; i < len; i++) { - nameAttr = links[i].getAttribute('data-name'); - hrefAttr = links[i].getAttribute('href'); - if (nameAttr === name || hrefAttr === cssUrl) { - return true; - } - } - return false; - }; - BrowserCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - if (this._linkTagExists(name, cssUrl)) { - externalCallback(); - return; - } - var linkNode = this.createLinkTag(name, cssUrl, externalCallback, externalErrorback); - this._insertLinkNode(linkNode); - }; - return BrowserCSSLoader; - }()); - var NodeCSSLoader = /** @class */ (function () { - function NodeCSSLoader() { - this.fs = require.nodeRequire('fs'); - } - NodeCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - var contents = this.fs.readFileSync(cssUrl, 'utf8'); - // Remove BOM - if (contents.charCodeAt(0) === NodeCSSLoader.BOM_CHAR_CODE) { - contents = contents.substring(1); - } - externalCallback(contents); - }; - NodeCSSLoader.BOM_CHAR_CODE = 65279; - return NodeCSSLoader; - }()); - // ------------------------------ Finally, the plugin - var CSSPlugin = /** @class */ (function () { - function CSSPlugin(cssLoader) { - this.cssLoader = cssLoader; - } - CSSPlugin.prototype.load = function (name, req, load, config) { - config = config || {}; - var myConfig = config['vs/css'] || {}; - global.inlineResources = myConfig.inlineResources; - global.inlineResourcesLimit = myConfig.inlineResourcesLimit || 5000; - var cssUrl = req.toUrl(name + '.css'); - this.cssLoader.load(name, cssUrl, function (contents) { - // Contents has the CSS file contents if we are in a build - if (config.isBuild) { - CSSPlugin.BUILD_MAP[name] = contents; - CSSPlugin.BUILD_PATH_MAP[name] = cssUrl; - } - load({}); - }, function (err) { - if (typeof load.error === 'function') { - load.error('Could not find ' + cssUrl + ' or it was empty'); - } - }); - }; - CSSPlugin.prototype.write = function (pluginName, moduleName, write) { - // getEntryPoint is a Monaco extension to r.js - var entryPoint = write.getEntryPoint(); - // r.js destroys the context of this plugin between calling 'write' and 'writeFile' - // so the only option at this point is to leak the data to a global - global.cssPluginEntryPoints = global.cssPluginEntryPoints || {}; - global.cssPluginEntryPoints[entryPoint] = global.cssPluginEntryPoints[entryPoint] || []; - global.cssPluginEntryPoints[entryPoint].push({ - moduleName: moduleName, - contents: CSSPlugin.BUILD_MAP[moduleName], - fsPath: CSSPlugin.BUILD_PATH_MAP[moduleName], - }); - write.asModule(pluginName + '!' + moduleName, 'define([\'vs/css!' + entryPoint + '\'], {});'); - }; - CSSPlugin.prototype.writeFile = function (pluginName, moduleName, req, write, config) { - if (global.cssPluginEntryPoints && global.cssPluginEntryPoints.hasOwnProperty(moduleName)) { - var fileName = req.toUrl(moduleName + '.css'); - var contents = [ - '/*---------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' *--------------------------------------------------------*/' - ], entries = global.cssPluginEntryPoints[moduleName]; - for (var i = 0; i < entries.length; i++) { - if (global.inlineResources) { - contents.push(Utilities.rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents, global.inlineResources === 'base64', global.inlineResourcesLimit)); - } - else { - contents.push(Utilities.rewriteUrls(entries[i].moduleName, moduleName, entries[i].contents)); - } - } - write(fileName, contents.join('\r\n')); - } - }; - CSSPlugin.prototype.getInlinedResources = function () { - return global.cssInlinedResources || []; - }; - CSSPlugin.BUILD_MAP = {}; - CSSPlugin.BUILD_PATH_MAP = {}; - return CSSPlugin; - }()); - CSSBuildLoaderPlugin.CSSPlugin = CSSPlugin; - var Utilities = /** @class */ (function () { - function Utilities() { - } - Utilities.startsWith = function (haystack, needle) { - return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; - }; - /** - * Find the path of a file. - */ - Utilities.pathOf = function (filename) { - var lastSlash = filename.lastIndexOf('/'); - if (lastSlash !== -1) { - return filename.substr(0, lastSlash + 1); - } - else { - return ''; - } - }; - /** - * A conceptual a + b for paths. - * Takes into account if `a` contains a protocol. - * Also normalizes the result: e.g.: a/b/ + ../c => a/c - */ - Utilities.joinPaths = function (a, b) { - function findSlashIndexAfterPrefix(haystack, prefix) { - if (Utilities.startsWith(haystack, prefix)) { - return Math.max(prefix.length, haystack.indexOf('/', prefix.length)); - } - return 0; - } - var aPathStartIndex = 0; - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, '//'); - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'http://'); - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'https://'); - function pushPiece(pieces, piece) { - if (piece === './') { - // Ignore - return; - } - if (piece === '../') { - var prevPiece = (pieces.length > 0 ? pieces[pieces.length - 1] : null); - if (prevPiece && prevPiece === '/') { - // Ignore - return; - } - if (prevPiece && prevPiece !== '../') { - // Pop - pieces.pop(); - return; - } - } - // Push - pieces.push(piece); - } - function push(pieces, path) { - while (path.length > 0) { - var slashIndex = path.indexOf('/'); - var piece = (slashIndex >= 0 ? path.substring(0, slashIndex + 1) : path); - path = (slashIndex >= 0 ? path.substring(slashIndex + 1) : ''); - pushPiece(pieces, piece); - } - } - var pieces = []; - push(pieces, a.substr(aPathStartIndex)); - if (b.length > 0 && b.charAt(0) === '/') { - pieces = []; - } - push(pieces, b); - return a.substring(0, aPathStartIndex) + pieces.join(''); - }; - Utilities.commonPrefix = function (str1, str2) { - var len = Math.min(str1.length, str2.length); - for (var i = 0; i < len; i++) { - if (str1.charCodeAt(i) !== str2.charCodeAt(i)) { - break; - } - } - return str1.substring(0, i); - }; - Utilities.commonFolderPrefix = function (fromPath, toPath) { - var prefix = Utilities.commonPrefix(fromPath, toPath); - var slashIndex = prefix.lastIndexOf('/'); - if (slashIndex === -1) { - return ''; - } - return prefix.substring(0, slashIndex + 1); - }; - Utilities.relativePath = function (fromPath, toPath) { - if (Utilities.startsWith(toPath, '/') || Utilities.startsWith(toPath, 'http://') || Utilities.startsWith(toPath, 'https://')) { - return toPath; - } - // Ignore common folder prefix - var prefix = Utilities.commonFolderPrefix(fromPath, toPath); - fromPath = fromPath.substr(prefix.length); - toPath = toPath.substr(prefix.length); - var upCount = fromPath.split('/').length; - var result = ''; - for (var i = 1; i < upCount; i++) { - result += '../'; - } - return result + toPath; - }; - Utilities._replaceURL = function (contents, replacer) { - // Use ")" as the terminator as quotes are oftentimes not used at all - return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, function (_) { - var matches = []; - for (var _i = 1; _i < arguments.length; _i++) { - matches[_i - 1] = arguments[_i]; - } - var url = matches[0]; - // Eliminate starting quotes (the initial whitespace is not captured) - if (url.charAt(0) === '"' || url.charAt(0) === '\'') { - url = url.substring(1); - } - // The ending whitespace is captured - while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { - url = url.substring(0, url.length - 1); - } - // Eliminate ending quotes - if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { - url = url.substring(0, url.length - 1); - } - if (!Utilities.startsWith(url, 'data:') && !Utilities.startsWith(url, 'http://') && !Utilities.startsWith(url, 'https://')) { - url = replacer(url); - } - return 'url(' + url + ')'; - }); - }; - Utilities.rewriteUrls = function (originalFile, newFile, contents) { - return this._replaceURL(contents, function (url) { - var absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); - return Utilities.relativePath(newFile, absoluteUrl); - }); - }; - Utilities.rewriteOrInlineUrls = function (originalFileFSPath, originalFile, newFile, contents, forceBase64, inlineByteLimit) { - var fs = require.nodeRequire('fs'); - var path = require.nodeRequire('path'); - return this._replaceURL(contents, function (url) { - if (/\.(svg|png)$/.test(url)) { - var fsPath = path.join(path.dirname(originalFileFSPath), url); - var fileContents = fs.readFileSync(fsPath); - if (fileContents.length < inlineByteLimit) { - global.cssInlinedResources = global.cssInlinedResources || []; - var normalizedFSPath = fsPath.replace(/\\/g, '/'); - if (global.cssInlinedResources.indexOf(normalizedFSPath) >= 0) { - // console.warn('CSS INLINING IMAGE AT ' + fsPath + ' MORE THAN ONCE. CONSIDER CONSOLIDATING CSS RULES'); - } - global.cssInlinedResources.push(normalizedFSPath); - var MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - var DATA = ';base64,' + fileContents.toString('base64'); - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - var newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(/%/g, '%25') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - var encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; - } - } - var absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); - return Utilities.relativePath(newFile, absoluteUrl); - }); - }; - return Utilities; - }()); - CSSBuildLoaderPlugin.Utilities = Utilities; - (function () { - var cssLoader = null; - var isElectron = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions['electron'] !== 'undefined'); - if (typeof process !== 'undefined' && process.versions && !!process.versions.node && !isElectron) { - cssLoader = new NodeCSSLoader(); - } - else { - cssLoader = new BrowserCSSLoader(); - } - define('vs/css', new CSSPlugin(cssLoader)); - })(); -})(CSSBuildLoaderPlugin || (CSSBuildLoaderPlugin = {})); diff --git a/src/vs/css.build.ts b/src/vs/css.build.ts new file mode 100644 index 00000000000..d4b3fcc96b2 --- /dev/null +++ b/src/vs/css.build.ts @@ -0,0 +1,304 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface ICSSPluginConfig { + inlineResources?: boolean | 'base64'; + inlineResourcesLimit?: number; +} + +interface ICSSEntryPointData { + moduleName: string; + contents: string; + fsPath: string; +} + +// This file gets compiled also with the standalone editor, +// so we cannot depend on types from node.d.ts +interface INodeFS { + readFileSync(path: string, encoding: 'utf8'): string; + readFileSync(path: string): INodeBuffer; +} +interface INodeBuffer { + length: number; + toString(encoding?: 'base64'): string; +} +interface INodePath { + dirname(p: string): string; + join(...paths: string[]): string; +} + +const nodeReq = (module: string): T | undefined => { + if (typeof (require).__$__nodeRequire === 'function') { + return (require).__$__nodeRequire(module); + } + return undefined; +}; + +const fs = nodeReq('fs'); +const path = nodeReq('path'); + +let inlineResources: boolean | 'base64' = false; +let inlineResourcesLimit: number = 5000; + +const contentsMap: { [moduleName: string]: string } = {}; +const pathMap: { [moduleName: string]: string } = {}; +const entryPoints: { [entryPoint: string]: ICSSEntryPointData[] } = {}; +const inlinedResources: string[] = []; + +/** + * Invoked by the loader at build-time + */ +export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { + if (!fs) { + throw new Error(`Cannot load files without 'fs'!`); + } + config = config || {}; + const myConfig = (config['vs/css'] || {}); + inlineResources = (typeof myConfig.inlineResources === 'undefined' ? false : myConfig.inlineResources); + inlineResourcesLimit = (myConfig.inlineResourcesLimit || 5000); + const cssUrl = req.toUrl(name + '.css'); + let contents = fs.readFileSync(cssUrl, 'utf8'); + if (contents.charCodeAt(0) === 65279 /* BOM */) { + // Remove BOM + contents = contents.substring(1); + } + if (config.isBuild) { + contentsMap[name] = contents; + pathMap[name] = cssUrl; + } + load({}); +} + +/** + * Invoked by the loader at build-time + */ +export function write(pluginName: string, moduleName: string, write: AMDLoader.IPluginWriteCallback): void { + const entryPoint = write.getEntryPoint(); + + entryPoints[entryPoint] = entryPoints[entryPoint] || []; + entryPoints[entryPoint].push({ + moduleName: moduleName, + contents: contentsMap[moduleName], + fsPath: pathMap[moduleName], + }); + + write.asModule(pluginName + '!' + moduleName, + 'define([\'vs/css!' + entryPoint + '\'], {});' + ); +} + +/** + * Invoked by the loader at build-time + */ +export function writeFile(pluginName: string, moduleName: string, req: AMDLoader.IRelativeRequire, write: AMDLoader.IPluginWriteFileCallback, config: AMDLoader.IConfigurationOptions): void { + if (entryPoints && entryPoints.hasOwnProperty(moduleName)) { + const fileName = req.toUrl(moduleName + '.css'); + const contents = [ + '/*---------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' *--------------------------------------------------------*/' + ], + entries = entryPoints[moduleName]; + for (let i = 0; i < entries.length; i++) { + if (inlineResources) { + contents.push(rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents, inlineResources === 'base64', inlineResourcesLimit)); + } else { + contents.push(rewriteUrls(entries[i].moduleName, moduleName, entries[i].contents)); + } + } + write(fileName, contents.join('\r\n')); + } +} + +export function getInlinedResources(): string[] { + return inlinedResources || []; +} + +function rewriteOrInlineUrls(originalFileFSPath: string, originalFile: string, newFile: string, contents: string, forceBase64: boolean, inlineByteLimit: number): string { + if (!fs || !path) { + throw new Error(`Cannot rewrite or inline urls without 'fs' or 'path'!`); + } + return CSSPluginUtilities.replaceURL(contents, (url) => { + if (/\.(svg|png)$/.test(url)) { + const fsPath = path.join(path.dirname(originalFileFSPath), url); + const fileContents = fs.readFileSync(fsPath); + + if (fileContents.length < inlineByteLimit) { + const normalizedFSPath = fsPath.replace(/\\/g, '/'); + inlinedResources.push(normalizedFSPath); + + const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; + let DATA = ';base64,' + fileContents.toString('base64'); + + if (!forceBase64 && /\.svg$/.test(url)) { + // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris + const newText = fileContents.toString() + .replace(/"/g, '\'') + .replace(/%/g, '%25') + .replace(//g, '%3E') + .replace(/&/g, '%26') + .replace(/#/g, '%23') + .replace(/\s+/g, ' '); + const encodedData = ',' + newText; + if (encodedData.length < DATA.length) { + DATA = encodedData; + } + } + return '"data:' + MIME + DATA + '"'; + } + } + + const absoluteUrl = CSSPluginUtilities.joinPaths(CSSPluginUtilities.pathOf(originalFile), url); + return CSSPluginUtilities.relativePath(newFile, absoluteUrl); + }); +} + +export function rewriteUrls(originalFile: string, newFile: string, contents: string): string { + return CSSPluginUtilities.replaceURL(contents, (url) => { + const absoluteUrl = CSSPluginUtilities.joinPaths(CSSPluginUtilities.pathOf(originalFile), url); + return CSSPluginUtilities.relativePath(newFile, absoluteUrl); + }); +} + +export class CSSPluginUtilities { + + public static startsWith(haystack: string, needle: string): boolean { + return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; + } + + /** + * Find the path of a file. + */ + public static pathOf(filename: string): string { + const lastSlash = filename.lastIndexOf('/'); + if (lastSlash !== -1) { + return filename.substr(0, lastSlash + 1); + } else { + return ''; + } + } + + /** + * A conceptual a + b for paths. + * Takes into account if `a` contains a protocol. + * Also normalizes the result: e.g.: a/b/ + ../c => a/c + */ + public static joinPaths(a: string, b: string): string { + + function findSlashIndexAfterPrefix(haystack: string, prefix: string): number { + if (CSSPluginUtilities.startsWith(haystack, prefix)) { + return Math.max(prefix.length, haystack.indexOf('/', prefix.length)); + } + return 0; + } + + let aPathStartIndex = 0; + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, '//'); + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'http://'); + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'https://'); + + function pushPiece(pieces: string[], piece: string): void { + if (piece === './') { + // Ignore + return; + } + if (piece === '../') { + const prevPiece = (pieces.length > 0 ? pieces[pieces.length - 1] : null); + if (prevPiece && prevPiece === '/') { + // Ignore + return; + } + if (prevPiece && prevPiece !== '../') { + // Pop + pieces.pop(); + return; + } + } + // Push + pieces.push(piece); + } + + function push(pieces: string[], path: string): void { + while (path.length > 0) { + const slashIndex = path.indexOf('/'); + const piece = (slashIndex >= 0 ? path.substring(0, slashIndex + 1) : path); + path = (slashIndex >= 0 ? path.substring(slashIndex + 1) : ''); + pushPiece(pieces, piece); + } + } + + let pieces: string[] = []; + push(pieces, a.substr(aPathStartIndex)); + if (b.length > 0 && b.charAt(0) === '/') { + pieces = []; + } + push(pieces, b); + + return a.substring(0, aPathStartIndex) + pieces.join(''); + } + + public static commonPrefix(str1: string, str2: string): string { + const len = Math.min(str1.length, str2.length); + for (let i = 0; i < len; i++) { + if (str1.charCodeAt(i) !== str2.charCodeAt(i)) { + return str1.substring(0, i); + } + } + return str1.substring(0, len); + } + + public static commonFolderPrefix(fromPath: string, toPath: string): string { + const prefix = CSSPluginUtilities.commonPrefix(fromPath, toPath); + const slashIndex = prefix.lastIndexOf('/'); + if (slashIndex === -1) { + return ''; + } + return prefix.substring(0, slashIndex + 1); + } + + public static relativePath(fromPath: string, toPath: string): string { + if (CSSPluginUtilities.startsWith(toPath, '/') || CSSPluginUtilities.startsWith(toPath, 'http://') || CSSPluginUtilities.startsWith(toPath, 'https://')) { + return toPath; + } + + // Ignore common folder prefix + const prefix = CSSPluginUtilities.commonFolderPrefix(fromPath, toPath); + fromPath = fromPath.substr(prefix.length); + toPath = toPath.substr(prefix.length); + + const upCount = fromPath.split('/').length; + let result = ''; + for (let i = 1; i < upCount; i++) { + result += '../'; + } + return result + toPath; + } + + public static replaceURL(contents: string, replacer: (url: string) => string): string { + // Use ")" as the terminator as quotes are oftentimes not used at all + return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => { + let url = matches[0]; + // Eliminate starting quotes (the initial whitespace is not captured) + if (url.charAt(0) === '"' || url.charAt(0) === '\'') { + url = url.substring(1); + } + // The ending whitespace is captured + while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { + url = url.substring(0, url.length - 1); + } + // Eliminate ending quotes + if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { + url = url.substring(0, url.length - 1); + } + + if (!CSSPluginUtilities.startsWith(url, 'data:') && !CSSPluginUtilities.startsWith(url, 'http://') && !CSSPluginUtilities.startsWith(url, 'https://')) { + url = replacer(url); + } + + return 'url(' + url + ')'; + }); + } +} diff --git a/src/vs/css.js b/src/vs/css.js deleted file mode 100644 index 8a0f9912902..00000000000 --- a/src/vs/css.js +++ /dev/null @@ -1,111 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/*--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - * Please make sure to make edits in the .ts file at https://github.com/microsoft/vscode-loader/ - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------*/ -'use strict'; -var CSSLoaderPlugin; -(function (CSSLoaderPlugin) { - var BrowserCSSLoader = /** @class */ (function () { - function BrowserCSSLoader() { - this._pendingLoads = 0; - } - BrowserCSSLoader.prototype.attachListeners = function (name, linkNode, callback, errorback) { - var unbind = function () { - linkNode.removeEventListener('load', loadEventListener); - linkNode.removeEventListener('error', errorEventListener); - }; - var loadEventListener = function (e) { - unbind(); - callback(); - }; - var errorEventListener = function (e) { - unbind(); - errorback(e); - }; - linkNode.addEventListener('load', loadEventListener); - linkNode.addEventListener('error', errorEventListener); - }; - BrowserCSSLoader.prototype._onLoad = function (name, callback) { - this._pendingLoads--; - callback(); - }; - BrowserCSSLoader.prototype._onLoadError = function (name, errorback, err) { - this._pendingLoads--; - errorback(err); - }; - BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { - this._pendingLoads++; - var head = document.head || document.getElementsByTagName('head')[0]; - head.appendChild(linkNode); - }; - BrowserCSSLoader.prototype.createLinkTag = function (name, cssUrl, externalCallback, externalErrorback) { - var _this = this; - var linkNode = document.createElement('link'); - linkNode.setAttribute('rel', 'stylesheet'); - linkNode.setAttribute('type', 'text/css'); - linkNode.setAttribute('data-name', name); - var callback = function () { return _this._onLoad(name, externalCallback); }; - var errorback = function (err) { return _this._onLoadError(name, externalErrorback, err); }; - this.attachListeners(name, linkNode, callback, errorback); - linkNode.setAttribute('href', cssUrl); - return linkNode; - }; - BrowserCSSLoader.prototype._linkTagExists = function (name, cssUrl) { - var i, len, nameAttr, hrefAttr, links = document.getElementsByTagName('link'); - for (i = 0, len = links.length; i < len; i++) { - nameAttr = links[i].getAttribute('data-name'); - hrefAttr = links[i].getAttribute('href'); - if (nameAttr === name || hrefAttr === cssUrl) { - return true; - } - } - return false; - }; - BrowserCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - if (this._linkTagExists(name, cssUrl)) { - externalCallback(); - return; - } - var linkNode = this.createLinkTag(name, cssUrl, externalCallback, externalErrorback); - this._insertLinkNode(linkNode); - }; - return BrowserCSSLoader; - }()); - // ------------------------------ Finally, the plugin - var CSSPlugin = /** @class */ (function () { - function CSSPlugin() { - this._cssLoader = new BrowserCSSLoader(); - } - CSSPlugin.prototype.load = function (name, req, load, config) { - config = config || {}; - var cssConfig = config['vs/css'] || {}; - if (cssConfig.disabled) { - // the plugin is asked to not create any style sheets - load({}); - return; - } - var cssUrl = req.toUrl(name + '.css'); - this._cssLoader.load(name, cssUrl, function (contents) { - load({}); - }, function (err) { - if (typeof load.error === 'function') { - load.error('Could not find ' + cssUrl + ' or it was empty'); - } - }); - }; - return CSSPlugin; - }()); - CSSLoaderPlugin.CSSPlugin = CSSPlugin; - define('vs/css', new CSSPlugin()); -})(CSSLoaderPlugin || (CSSLoaderPlugin = {})); diff --git a/src/vs/css.ts b/src/vs/css.ts new file mode 100644 index 00000000000..4a5ea48d174 --- /dev/null +++ b/src/vs/css.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface ICSSPluginConfig { + disabled?: boolean; +} + +/** + * Invoked by the loader at run-time + */ +export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { + config = config || {}; + const cssConfig = (config['vs/css'] || {}); + + if (cssConfig.disabled) { + // the plugin is asked to not create any style sheets + load({}); + return; + } + + const cssUrl = req.toUrl(name + '.css'); + loadCSS(name, cssUrl, () => { + load({}); + }, (err: any) => { + if (typeof load.error === 'function') { + load.error('Could not find ' + cssUrl + '.'); + } + }); +} + +function loadCSS(name: string, cssUrl: string, callback: () => void, errorback: (err: any) => void): void { + if (linkTagExists(name, cssUrl)) { + callback(); + return; + } + createLinkTag(name, cssUrl, callback, errorback); +} + +function linkTagExists(name: string, cssUrl: string): boolean { + const links = document.getElementsByTagName('link'); + for (let i = 0, len = links.length; i < len; i++) { + const nameAttr = links[i].getAttribute('data-name'); + const hrefAttr = links[i].getAttribute('href'); + if (nameAttr === name || hrefAttr === cssUrl) { + return true; + } + } + return false; +} + +function createLinkTag(name: string, cssUrl: string, callback: () => void, errorback: (err: any) => void): void { + const linkNode = document.createElement('link'); + linkNode.setAttribute('rel', 'stylesheet'); + linkNode.setAttribute('type', 'text/css'); + linkNode.setAttribute('data-name', name); + + attachListeners(name, linkNode, callback, errorback); + linkNode.setAttribute('href', cssUrl); + + const head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(linkNode); +} + +function attachListeners(name: string, linkNode: HTMLLinkElement, callback: () => void, errorback: (err: any) => void): void { + const unbind = () => { + linkNode.removeEventListener('load', loadEventListener); + linkNode.removeEventListener('error', errorEventListener); + }; + const loadEventListener = (e: any) => { + unbind(); + callback(); + }; + const errorEventListener = (e: any) => { + unbind(); + errorback(e); + }; + linkNode.addEventListener('load', loadEventListener); + linkNode.addEventListener('error', errorEventListener); +} diff --git a/src/vs/editor/browser/config/domFontInfo.ts b/src/vs/editor/browser/config/domFontInfo.ts index 96c7021e53b..75982616f0c 100644 --- a/src/vs/editor/browser/config/domFontInfo.ts +++ b/src/vs/editor/browser/config/domFontInfo.ts @@ -3,21 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import { FastDomNode } from 'vs/base/browser/fastDomNode'; -import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; export function applyFontInfo(domNode: FastDomNode | HTMLElement, fontInfo: BareFontInfo): void { if (domNode instanceof FastDomNode) { - domNode.setFontFamily(fontInfo.getMassagedFontFamily(browser.isSafari ? EDITOR_FONT_DEFAULTS.fontFamily : null)); + domNode.setFontFamily(fontInfo.getMassagedFontFamily()); domNode.setFontWeight(fontInfo.fontWeight); domNode.setFontSize(fontInfo.fontSize); domNode.setFontFeatureSettings(fontInfo.fontFeatureSettings); domNode.setLineHeight(fontInfo.lineHeight); domNode.setLetterSpacing(fontInfo.letterSpacing); } else { - domNode.style.fontFamily = fontInfo.getMassagedFontFamily(browser.isSafari ? EDITOR_FONT_DEFAULTS.fontFamily : null); + domNode.style.fontFamily = fontInfo.getMassagedFontFamily(); domNode.style.fontWeight = fontInfo.fontWeight; domNode.style.fontSize = fontInfo.fontSize + 'px'; domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings; diff --git a/src/vs/editor/browser/config/fontMeasurements.ts b/src/vs/editor/browser/config/fontMeasurements.ts index 39e1737cd33..00dde46dcc9 100644 --- a/src/vs/editor/browser/config/fontMeasurements.ts +++ b/src/vs/editor/browser/config/fontMeasurements.ts @@ -149,9 +149,7 @@ class FontMeasurementsImpl extends Disposable { private _createRequest(chr: string, type: CharWidthRequestType, all: CharWidthRequest[], monospace: CharWidthRequest[] | null): CharWidthRequest { const result = new CharWidthRequest(chr, type); all.push(result); - if (monospace) { - monospace.push(result); - } + monospace?.push(result); return result; } diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 49d6a503a84..42f1bc526a8 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { forEach } from 'vs/base/common/collections'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; export interface ISettingsReader { @@ -152,14 +151,14 @@ const suggestFilteredTypesMapping: Record = { registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => { if (value && typeof value === 'object') { - forEach(suggestFilteredTypesMapping, entry => { - const v = value[entry.key]; + for (const entry of Object.entries(suggestFilteredTypesMapping)) { + const v = value[entry[0]]; if (v === false) { - if (typeof read(`suggest.${entry.value}`) === 'undefined') { - write(`suggest.${entry.value}`, false); + if (typeof read(`suggest.${entry[1]}`) === 'undefined') { + write(`suggest.${entry[1]}`, false); } } - }); + } write('suggest.filteredTypes', undefined); } }); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 29fa0e33b6a..8b1bab406b2 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { TimeoutTimer } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { HitTestContext, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import { IMouseTarget, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -55,6 +55,7 @@ export class MouseHandler extends ViewEventHandler { protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; private _height: number; + private _mouseLeaveMonitor: IDisposable | null = null; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { super(); @@ -79,7 +80,25 @@ export class MouseHandler extends ViewEventHandler { this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true))); - this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => this._onMouseMove(e))); + this._register(mouseEvents.onMouseMove(this.viewHelper.viewDomNode, (e) => { + this._onMouseMove(e); + + // See https://github.com/microsoft/vscode/issues/138789 + // When moving the mouse really quickly, the browser sometimes forgets to + // send us a `mouseleave` or `mouseout` event. We therefore install here + // a global `mousemove` listener to manually recover if the mouse goes outside + // the editor. As soon as the mouse leaves outside of the editor, we + // remove this listener + + if (!this._mouseLeaveMonitor) { + this._mouseLeaveMonitor = dom.addDisposableListener(document, 'mousemove', (e) => { + if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) { + // went outside the editor! + this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); + } + }); + } + })); this._register(mouseEvents.onMouseUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e))); @@ -132,6 +151,10 @@ export class MouseHandler extends ViewEventHandler { public override dispose(): void { this._context.removeEventHandler(this); + if (this._mouseLeaveMonitor) { + this._mouseLeaveMonitor.dispose(); + this._mouseLeaveMonitor = null; + } super.dispose(); } @@ -220,6 +243,10 @@ export class MouseHandler extends ViewEventHandler { } public _onMouseLeave(e: EditorMouseEvent): void { + if (this._mouseLeaveMonitor) { + this._mouseLeaveMonitor.dispose(); + this._mouseLeaveMonitor = null; + } this.lastMouseLeaveTime = (new Date()).getTime(); this.viewController.emitMouseLeave({ event: e, diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 08edad1d7fd..c13cd539659 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -823,7 +823,21 @@ export class MouseTargetFactory { const curr = points[i]; if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); - return request.fulfillContentText(pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); + + // See https://github.com/microsoft/vscode/issues/152819 + // Due to the use of zwj, the browser's hit test result is skewed towards the left + // Here we try to correct that if the mouse horizontal offset is closer to the right than the left + + const prevDelta = Math.abs(prev.offset - request.mouseContentHorizontalOffset); + const nextDelta = Math.abs(curr.offset - request.mouseContentHorizontalOffset); + + const resultPos = ( + prevDelta < nextDelta + ? new Position(lineNumber, prev.column) + : new Position(lineNumber, curr.column) + ); + + return request.fulfillContentText(resultPos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } } return request.fulfillContentText(pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index ffbe3b55882..8ce775febd3 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -522,7 +522,7 @@ export class TextAreaHandler extends ViewPart { this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { - // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 500 for a better experience + // If a screen reader is attached and the default value is not set we should automatically increase the page size to 500 for a better experience this._accessibilityPageSize = 500; } else { this._accessibilityPageSize = accessibilityPageSize; @@ -634,9 +634,7 @@ export class TextAreaHandler extends ViewPart { public prepareRender(ctx: RenderingContext): void { this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition); - if (this._visibleTextArea) { - this._visibleTextArea.prepareRender(ctx); - } + this._visibleTextArea?.prepareRender(ctx); } public render(ctx: RestrictedRenderingContext): void { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 3e7e9a3252e..1e785855191 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -5,6 +5,7 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -672,19 +673,19 @@ class ClipboardEventUtils { export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper { - public readonly onKeyDown = this._register(dom.createEventEmitter(this._actual, 'keydown')).event; - public readonly onKeyPress = this._register(dom.createEventEmitter(this._actual, 'keypress')).event; - public readonly onKeyUp = this._register(dom.createEventEmitter(this._actual, 'keyup')).event; - public readonly onCompositionStart = this._register(dom.createEventEmitter(this._actual, 'compositionstart')).event; - public readonly onCompositionUpdate = this._register(dom.createEventEmitter(this._actual, 'compositionupdate')).event; - public readonly onCompositionEnd = this._register(dom.createEventEmitter(this._actual, 'compositionend')).event; - public readonly onBeforeInput = this._register(dom.createEventEmitter(this._actual, 'beforeinput')).event; - public readonly onInput = >this._register(dom.createEventEmitter(this._actual, 'input')).event; - public readonly onCut = this._register(dom.createEventEmitter(this._actual, 'cut')).event; - public readonly onCopy = this._register(dom.createEventEmitter(this._actual, 'copy')).event; - public readonly onPaste = this._register(dom.createEventEmitter(this._actual, 'paste')).event; - public readonly onFocus = this._register(dom.createEventEmitter(this._actual, 'focus')).event; - public readonly onBlur = this._register(dom.createEventEmitter(this._actual, 'blur')).event; + public readonly onKeyDown = this._register(new DomEmitter(this._actual, 'keydown')).event; + public readonly onKeyPress = this._register(new DomEmitter(this._actual, 'keypress')).event; + public readonly onKeyUp = this._register(new DomEmitter(this._actual, 'keyup')).event; + public readonly onCompositionStart = this._register(new DomEmitter(this._actual, 'compositionstart')).event; + public readonly onCompositionUpdate = this._register(new DomEmitter(this._actual, 'compositionupdate')).event; + public readonly onCompositionEnd = this._register(new DomEmitter(this._actual, 'compositionend')).event; + public readonly onBeforeInput = this._register(new DomEmitter(this._actual, 'beforeinput')).event; + public readonly onInput = >this._register(new DomEmitter(this._actual, 'input')).event; + public readonly onCut = this._register(new DomEmitter(this._actual, 'cut')).event; + public readonly onCopy = this._register(new DomEmitter(this._actual, 'copy')).event; + public readonly onPaste = this._register(new DomEmitter(this._actual, 'paste')).event; + public readonly onFocus = this._register(new DomEmitter(this._actual, 'focus')).event; + public readonly onBlur = this._register(new DomEmitter(this._actual, 'blur')).event; private _onSyntheticTap = this._register(new Emitter()); public readonly onSyntheticTap: Event = this._onSyntheticTap.event; diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index ecc72a1a366..b5ab2a699e1 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -84,7 +84,7 @@ export namespace EditorScroll_ { \`\`\` * 'by': Unit to move. Default is computed based on 'to' value. \`\`\` - 'line', 'wrappedLine', 'page', 'halfPage' + 'line', 'wrappedLine', 'page', 'halfPage', 'editor' \`\`\` * 'value': Number of units to move. Default is '1'. * 'revealCursor': If 'true' reveals the cursor if it is outside view port. @@ -100,7 +100,7 @@ export namespace EditorScroll_ { }, 'by': { 'type': 'string', - 'enum': ['line', 'wrappedLine', 'page', 'halfPage'] + 'enum': ['line', 'wrappedLine', 'page', 'halfPage', 'editor'] }, 'value': { 'type': 'number', @@ -130,7 +130,8 @@ export namespace EditorScroll_ { Line: 'line', WrappedLine: 'wrappedLine', Page: 'page', - HalfPage: 'halfPage' + HalfPage: 'halfPage', + Editor: 'editor' }; /** @@ -172,6 +173,9 @@ export namespace EditorScroll_ { case RawUnit.HalfPage: unit = Unit.HalfPage; break; + case RawUnit.Editor: + unit = Unit.Editor; + break; default: unit = Unit.WrappedLine; } @@ -205,7 +209,8 @@ export namespace EditorScroll_ { Line = 1, WrappedLine = 2, Page = 3, - HalfPage = 4 + HalfPage = 4, + Editor = 5 } } @@ -1279,6 +1284,14 @@ export namespace CoreNavigationCommands { return viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); } + if (args.unit === EditorScroll_.Unit.Editor) { + let desiredTopModelLineNumber = 0; + if (args.direction === EditorScroll_.Direction.Down) { + desiredTopModelLineNumber = viewModel.model.getLineCount() - viewModel.cursorConfig.pageSize; + } + return viewModel.viewLayout.getVerticalOffsetForLineNumber(desiredTopModelLineNumber); + } + let noOfLines: number; if (args.unit === EditorScroll_.Unit.Page) { noOfLines = viewModel.cursorConfig.pageSize * args.value; @@ -1345,6 +1358,29 @@ export namespace CoreNavigationCommands { } }); + export const ScrollEditorTop: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollEditorTop', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + } + }); + } + + runCoreEditorCommand(viewModel: IViewModel, args: any): void { + EditorScroll._runEditorScroll(viewModel, args.source, { + direction: EditorScroll_.Direction.Up, + unit: EditorScroll_.Unit.Editor, + value: 1, + revealCursor: false, + select: false + }); + } + }); + export const ScrollLineDown: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { constructor() { super({ @@ -1396,6 +1432,29 @@ export namespace CoreNavigationCommands { } }); + export const ScrollEditorBottom: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { + constructor() { + super({ + id: 'scrollEditorBottom', + precondition: undefined, + kbOpts: { + weight: CORE_WEIGHT, + kbExpr: EditorContextKeys.textInputFocus, + } + }); + } + + runCoreEditorCommand(viewModel: IViewModel, args: any): void { + EditorScroll._runEditorScroll(viewModel, args.source, { + direction: EditorScroll_.Direction.Down, + unit: EditorScroll_.Unit.Editor, + value: 1, + revealCursor: false, + select: false + }); + } + }); + class WordCommand extends CoreEditorCommand { private readonly _inSelectionMode: boolean; diff --git a/src/vs/editor/browser/dnd.ts b/src/vs/editor/browser/dnd.ts index 80a5958504d..de99d817fcd 100644 --- a/src/vs/editor/browser/dnd.ts +++ b/src/vs/editor/browser/dnd.ts @@ -3,9 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DataTransfers } from 'vs/base/browser/dnd'; +import { distinct } from 'vs/base/common/arrays'; import { createFileDataTransferItem, createStringDataTransferItem, IDataTransferItem, VSDataTransfer } from 'vs/base/common/dataTransfer'; +import { Mimes } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; -import { FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; +import { CodeDataTransfers, extractEditorsDropData, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; export function toVSDataTransfer(dataTransfer: DataTransfer) { @@ -31,3 +34,43 @@ export function createFileDataTransferItemFromFile(file: File): IDataTransferIte return new Uint8Array(await file.arrayBuffer()); }); } + +const INTERNAL_DND_MIME_TYPES = Object.freeze([ + CodeDataTransfers.EDITORS, + CodeDataTransfers.FILES, + DataTransfers.RESOURCES, +]); + +export function addExternalEditorsDropData(dataTransfer: VSDataTransfer, dragEvent: DragEvent, overwriteUriList = false) { + if (dragEvent.dataTransfer && (overwriteUriList || !dataTransfer.has(Mimes.uriList))) { + const editorData = extractEditorsDropData(dragEvent) + .filter(input => input.resource) + .map(input => input.resource!.toString()); + + // Also add in the files + for (const item of dragEvent.dataTransfer?.items) { + const file = item.getAsFile(); + if (file) { + editorData.push((file as FileAdditionalNativeProperties).path ? URI.file((file as FileAdditionalNativeProperties).path!).toString() : file.name); + } + } + + if (editorData.length) { + dataTransfer.replace(Mimes.uriList, createStringDataTransferItem(UriList.create(editorData))); + } + } + + for (const internal of INTERNAL_DND_MIME_TYPES) { + dataTransfer.delete(internal); + } +} + +export const UriList = Object.freeze({ + // http://amundsen.com/hypermedia/urilist/ + create: (entries: ReadonlyArray): string => { + return distinct(entries.map(x => x.toString())).join('\r\n'); + }, + parse: (str: string): string[] => { + return str.split('\r\n').filter(value => !value.startsWith('#')); + } +}); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index e620b237b81..c5ab40999b1 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -19,7 +19,7 @@ import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManage import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel'; import { InjectedText } from 'vs/editor/common/modelLineProjectionData'; -import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer'; +import { ILineChange, IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { IDimension } from 'vs/editor/common/core/dimension'; /** @@ -852,24 +852,29 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * All decorations added through this call will get the ownerId of this editor. - * @see {@link ITextModel.deltaDecorations} + * @deprecated */ deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[]; /** - * @internal + * Remove previously added decorations. */ - setDecorations(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; + removeDecorations(decorationIds: string[]): void; /** * @internal */ - setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void; + setDecorationsByType(description: string, decorationTypeKey: string, ranges: editorCommon.IDecorationOptions[]): void; /** * @internal */ - removeDecorations(decorationTypeKey: string): void; + setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void; + + /** + * @internal + */ + removeDecorationsByType(decorationTypeKey: string): void; /** * Get the layout info for the editor. @@ -894,10 +899,15 @@ export interface ICodeEditor extends editorCommon.IEditor { getWhitespaces(): IEditorWhitespace[]; /** - * Get the vertical position (top offset) for the line w.r.t. to the first line. + * Get the vertical position (top offset) for the line's top w.r.t. to the first line. */ getTopForLineNumber(lineNumber: number): number; + /** + * Get the vertical position (top offset) for the line's bottom w.r.t. to the first line. + */ + getBottomForLineNumber(lineNumber: number): number; + /** * Get the vertical position (top offset) for the position w.r.t. to the first line. */ @@ -905,9 +915,10 @@ export interface ICodeEditor extends editorCommon.IEditor { /** * Set the model ranges that will be hidden in the view. + * Hidden areas are stored per source. * @internal */ - setHiddenAreas(ranges: IRange[]): void; + setHiddenAreas(ranges: IRange[], source?: unknown): void; /** * Sets the editor aria options, primarily the active descendent. diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 501260e7263..4cfc92f8a6e 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -24,7 +24,7 @@ export class PageCoordinates { ) { } public toClientCoordinates(): ClientCoordinates { - return new ClientCoordinates(this.x - dom.StandardWindow.scrollX, this.y - dom.StandardWindow.scrollY); + return new ClientCoordinates(this.x - window.scrollX, this.y - window.scrollY); } } @@ -44,7 +44,7 @@ export class ClientCoordinates { ) { } public toPageCoordinates(): PageCoordinates { - return new PageCoordinates(this.clientX + dom.StandardWindow.scrollX, this.clientY + dom.StandardWindow.scrollY); + return new PageCoordinates(this.clientX + window.scrollX, this.clientY + window.scrollY); } } @@ -173,7 +173,7 @@ export class EditorMouseEventFactory { } public onMouseLeave(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { - return dom.addDisposableNonBubblingMouseOutListener(target, (e: MouseEvent) => { + return dom.addDisposableListener(target, dom.EventType.MOUSE_LEAVE, (e: MouseEvent) => { callback(this._create(e)); }); } @@ -208,7 +208,7 @@ export class EditorPointerEventFactory { } public onPointerLeave(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { - return dom.addDisposableNonBubblingPointerOutListener(target, (e: MouseEvent) => { + return dom.addDisposableListener(target, dom.EventType.POINTER_LEAVE, (e: MouseEvent) => { callback(this._create(e)); }); } diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 93b20f1d2ed..f61142282d3 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -339,8 +339,9 @@ export abstract class EditorAction extends EditorCommand { protected reportTelemetry(accessor: ServicesAccessor, editor: ICodeEditor) { type EditorActionInvokedClassification = { owner: 'alexdima'; - name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'An editor action has been invoked.'; + name: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The label of the action that was invoked.' }; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was invoked.' }; }; type EditorActionInvokedEvent = { name: string; diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index da58f07c2e7..aaa31ee4ce4 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -5,11 +5,12 @@ import * as dom from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { ICodeEditorOpenHandler, ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -42,6 +43,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC protected _globalStyleSheet: GlobalStyleSheet | null; private readonly _decorationOptionProviders = new Map(); private readonly _editorStyleSheets = new Map(); + private readonly _codeEditorOpenHandlers = new LinkedList(); constructor( @IThemeService private readonly _themeService: IThemeService, @@ -161,7 +163,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC if (provider.refCount <= 0) { this._decorationOptionProviders.delete(key); provider.dispose(); - this.listCodeEditors().forEach((ed) => ed.removeDecorations(key)); + this.listCodeEditors().forEach((ed) => ed.removeDecorationsByType(key)); } } } @@ -247,7 +249,21 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + + async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + for (const handler of this._codeEditorOpenHandlers) { + const candidate = await handler(input, source, sideBySide); + if (candidate !== null) { + return candidate; + } + } + return null; + } + + registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable { + const rm = this._codeEditorOpenHandlers.unshift(handler); + return toDisposable(rm); + } } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 558376fae33..12d1417489a 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, WorkspaceFileEdit, WorkspaceFileEditOptions, WorkspaceTextEdit } from 'vs/editor/common/languages'; +import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, IWorkspaceFileEdit, WorkspaceFileEditOptions, IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -15,49 +15,77 @@ import { CancellationToken } from 'vs/base/common/cancellation'; export const IBulkEditService = createDecorator('IWorkspaceEditService'); -function isWorkspaceFileEdit(thing: any): thing is WorkspaceFileEdit { - return isObject(thing) && (Boolean((thing).newUri) || Boolean((thing).oldUri)); -} - -function isWorkspaceTextEdit(thing: any): thing is WorkspaceTextEdit { - return isObject(thing) && URI.isUri((thing).resource) && isObject((thing).edit); -} - export class ResourceEdit { protected constructor(readonly metadata?: WorkspaceEditMetadata) { } static convert(edit: WorkspaceEdit): ResourceEdit[] { - return edit.edits.map(edit => { - if (isWorkspaceTextEdit(edit)) { - return new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata); + if (ResourceTextEdit.is(edit)) { + return ResourceTextEdit.lift(edit); } - if (isWorkspaceFileEdit(edit)) { - return new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata); + + if (ResourceFileEdit.is(edit)) { + return ResourceFileEdit.lift(edit); } throw new Error('Unsupported edit'); }); } } -export class ResourceTextEdit extends ResourceEdit { +export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit { + + static is(candidate: any): candidate is IWorkspaceTextEdit { + if (candidate instanceof ResourceTextEdit) { + return true; + } + return isObject(candidate) + && URI.isUri((candidate).resource) + && isObject((candidate).textEdit); + } + + static lift(edit: IWorkspaceTextEdit): ResourceTextEdit { + if (edit instanceof ResourceTextEdit) { + return edit; + } else { + return new ResourceTextEdit(edit.resource, edit.textEdit, edit.versionId, edit.metadata); + } + } + constructor( readonly resource: URI, - readonly textEdit: TextEdit, - readonly versionId?: number, - metadata?: WorkspaceEditMetadata + readonly textEdit: TextEdit & { insertAsSnippet?: boolean }, + readonly versionId: number | undefined = undefined, + metadata?: WorkspaceEditMetadata, ) { super(metadata); } } -export class ResourceFileEdit extends ResourceEdit { +export class ResourceFileEdit extends ResourceEdit implements IWorkspaceFileEdit { + + static is(candidate: any): candidate is IWorkspaceFileEdit { + if (candidate instanceof ResourceFileEdit) { + return true; + } else { + return isObject(candidate) + && (Boolean((candidate).newResource) || Boolean((candidate).oldResource)); + } + } + + static lift(edit: IWorkspaceFileEdit): ResourceFileEdit { + if (edit instanceof ResourceFileEdit) { + return edit; + } else { + return new ResourceFileEdit(edit.oldResource, edit.newResource, edit.options, edit.metadata); + } + } + constructor( readonly oldResource: URI | undefined, readonly newResource: URI | undefined, - readonly options?: WorkspaceFileEditOptions, + readonly options: WorkspaceFileEditOptions = {}, metadata?: WorkspaceEditMetadata ) { super(metadata); @@ -91,5 +119,5 @@ export interface IBulkEditService { setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable; - apply(edit: ResourceEdit[], options?: IBulkEditOptions): Promise; + apply(edit: ResourceEdit[] | WorkspaceEdit, options?: IBulkEditOptions): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index b56596939a8..40d7947efcd 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -10,6 +10,7 @@ import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const ICodeEditorService = createDecorator('codeEditorService'); @@ -53,4 +54,9 @@ export interface ICodeEditorService { getActiveCodeEditor(): ICodeEditor | null; openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable; +} + +export interface ICodeEditorOpenHandler { + (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index be0dbf6cbac..2ca1ded563b 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -10,12 +10,11 @@ import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/b import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { IChange, IDiffComputationResult } from 'vs/editor/common/diff/diffComputer'; import { ITextModel } from 'vs/editor/common/model'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { regExpFlags } from 'vs/base/common/strings'; @@ -26,6 +25,8 @@ import { canceled } from 'vs/base/common/errors'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; +import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; /** * Stop syncing a model to the worker if it was not needed for 1 min. @@ -94,8 +95,8 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); } - public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { - return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime)); + public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { + return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options)); } public canComputeDirtyDiff(original: URI, modified: URI): boolean { @@ -491,9 +492,9 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien }); } - public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { + public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime); + return proxy.computeDiff(original.toString(), modified.toString(), options); }); } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 56870b5170c..8b751e86828 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -163,7 +163,7 @@ export class OpenerService implements IOpenerService { // validate against the original URI that this URI resolves to, if one exists const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; for (const validator of this._validators) { - if (!(await validator.shouldOpen(validationTarget))) { + if (!(await validator.shouldOpen(validationTarget, options))) { return false; } } diff --git a/src/vs/editor/browser/services/webWorker.ts b/src/vs/editor/browser/services/webWorker.ts index c853e3220ec..b5757c58865 100644 --- a/src/vs/editor/browser/services/webWorker.ts +++ b/src/vs/editor/browser/services/webWorker.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getAllMethodNames } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { EditorWorkerClient } from 'vs/editor/browser/services/editorWorkerService'; -import { IModelService } from 'vs/editor/common/services/model'; -import * as types from 'vs/base/common/types'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; +import { IModelService } from 'vs/editor/common/services/model'; /** * Create a new web worker that has model syncing capabilities built in. @@ -92,7 +92,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implement private _getForeignProxy(): Promise { if (!this._foreignProxy) { this._foreignProxy = this._getProxy().then((proxy) => { - const foreignHostMethods = this._foreignModuleHost ? types.getAllMethodNames(this._foreignModuleHost) : []; + const foreignHostMethods = this._foreignModuleHost ? getAllMethodNames(this._foreignModuleHost) : []; return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData, foreignHostMethods).then((foreignMethods) => { this._foreignModuleCreateData = null; diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 7fc13b07422..a05fae66338 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -49,6 +49,7 @@ import { IViewModel } from 'vs/editor/common/viewModel'; import { getThemeTypeSelector, IColorTheme } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; +import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/blockDecorations'; export interface IContentWidgetData { @@ -180,6 +181,9 @@ export class View extends ViewEventHandler { const rulers = new Rulers(this._context); this._viewParts.push(rulers); + const blockOutline = new BlockDecorations(this._context); + this._viewParts.push(blockOutline); + const minimap = new Minimap(this._context); this._viewParts.push(minimap); @@ -192,6 +196,7 @@ export class View extends ViewEventHandler { this._linesContent.appendChild(contentViewOverlays.getDomNode()); this._linesContent.appendChild(rulers.domNode); + this._linesContent.appendChild(blockOutline.domNode); this._linesContent.appendChild(this._viewZones.domNode); this._linesContent.appendChild(this._viewLines.getDomNode()); this._linesContent.appendChild(this._contentWidgets.domNode); diff --git a/src/vs/editor/browser/view/renderingContext.ts b/src/vs/editor/browser/view/renderingContext.ts index bed244ba618..5cb7c840858 100644 --- a/src/vs/editor/browser/view/renderingContext.ts +++ b/src/vs/editor/browser/view/renderingContext.ts @@ -53,8 +53,12 @@ export abstract class RestrictedRenderingContext { return absoluteTop - this.scrollTop; } - public getVerticalOffsetForLineNumber(lineNumber: number): number { - return this._viewLayout.getVerticalOffsetForLineNumber(lineNumber); + public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones?: boolean): number { + return this._viewLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones); + } + + public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones?: boolean): number { + return this._viewLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones); } public getDecorationsInViewport(): ViewModelDecoration[] { diff --git a/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.css b/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.css new file mode 100644 index 00000000000..f2100bd4846 --- /dev/null +++ b/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.css @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .blockDecorations-container { + position: absolute; + top: 0; +} + +.monaco-editor .blockDecorations-block { + position: absolute; + box-sizing: border-box; +} diff --git a/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.ts b/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.ts new file mode 100644 index 00000000000..a9d0ac084cc --- /dev/null +++ b/src/vs/editor/browser/viewParts/blockDecorations/blockDecorations.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; +import 'vs/css!./blockDecorations'; +import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; +import { ViewPart } from 'vs/editor/browser/view/viewPart'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import * as viewEvents from 'vs/editor/common/viewEvents'; +import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; + +export class BlockDecorations extends ViewPart { + + public domNode: FastDomNode; + + private readonly blocks: FastDomNode[] = []; + + private contentWidth: number = -1; + + constructor(context: ViewContext) { + super(context); + + this.domNode = createFastDomNode(document.createElement('div')); + this.domNode.setAttribute('role', 'presentation'); + this.domNode.setAttribute('aria-hidden', 'true'); + this.domNode.setClassName('blockDecorations-container'); + + this.update(); + } + + private update(): boolean { + let didChange = false; + const options = this._context.configuration.options; + const layoutInfo = options.get(EditorOption.layoutInfo); + const newContentWidth = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth; + + if (this.contentWidth !== newContentWidth) { + this.contentWidth = newContentWidth; + didChange = true; + } + + return didChange; + } + + public override dispose(): void { + super.dispose(); + } + + // --- begin event handlers + + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + return this.update(); + } + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + return e.scrollTopChanged || e.scrollLeftChanged; + } + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + return true; + } + + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + return true; + } + + // --- end event handlers + public prepareRender(ctx: RenderingContext): void { + // Nothing to read + } + + public render(ctx: RestrictedRenderingContext): void { + let count = 0; + const decorations = ctx.getDecorationsInViewport(); + for (const decoration of decorations) { + if (!decoration.options.blockClassName) { + continue; + } + + let block = this.blocks[count]; + if (!block) { + block = this.blocks[count] = createFastDomNode(document.createElement('div')); + this.domNode.appendChild(block); + } + const top = ctx.getVerticalOffsetForLineNumber(decoration.range.startLineNumber, true); + const bottom = ctx.getVerticalOffsetAfterLineNumber(decoration.range.endLineNumber, true); + + block.setClassName('blockDecorations-block ' + decoration.options.blockClassName); + block.setLeft(ctx.scrollLeft); + block.setWidth(this.contentWidth); + block.setTop(top); + block.setHeight(bottom - top); + + count++; + } + + for (let i = count; i < this.blocks.length; i++) { + this.blocks[i].domNode.remove(); + } + this.blocks.length = count; + } +} diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index 056c24f5c73..f45578d9f15 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -340,7 +340,7 @@ class Widget { const MIN_LIMIT = Math.max(0, domNodePosition.left - width); const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width); - let absoluteLeft = domNodePosition.left + left - dom.StandardWindow.scrollX; + let absoluteLeft = domNodePosition.left + left - window.scrollX; if (absoluteLeft + width > MAX_LIMIT) { const delta = absoluteLeft - (MAX_LIMIT - width); @@ -362,8 +362,8 @@ class Widget { const belowTop = bottomLeft.top + this._lineHeight; const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode); - const absoluteAboveTop = domNodePosition.top + aboveTop - dom.StandardWindow.scrollY; - const absoluteBelowTop = domNodePosition.top + belowTop - dom.StandardWindow.scrollY; + const absoluteAboveTop = domNodePosition.top + aboveTop - window.scrollY; + const absoluteBelowTop = domNodePosition.top + belowTop - window.scrollY; const windowSize = dom.getClientArea(document.body); const [aboveLeft, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, topLeft.left - ctx.scrollLeft + this._contentLeft, width); diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 3f5d8862f26..0ac2afa5fa3 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -27,6 +27,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { private _lineNumbersLeft!: number; private _lineNumbersWidth!: number; private _lastCursorModelPosition: Position; + private _lastCursorViewPosition: Position; private _renderResult: string[] | null; private _activeLineNumber: number; @@ -37,6 +38,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._readConfig(); this._lastCursorModelPosition = new Position(1, 1); + this._lastCursorViewPosition = new Position(1, 1); this._renderResult = null; this._activeLineNumber = 1; this._context.addEventHandler(this); @@ -68,6 +70,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { const primaryViewPosition = e.selections[0].getPosition(); + this._lastCursorViewPosition = primaryViewPosition; this._lastCursorModelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(primaryViewPosition); let shouldRender = false; @@ -112,14 +115,6 @@ export class LineNumbersOverlay extends DynamicViewOverlay { return this._renderCustomLineNumbers(modelLineNumber); } - if (this._renderLineNumbers === RenderLineNumbersType.Relative) { - const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber); - if (diff === 0) { - return '' + modelLineNumber + ''; - } - return String(diff); - } - if (this._renderLineNumbers === RenderLineNumbersType.Interval) { if (this._lastCursorModelPosition.lineNumber === modelLineNumber) { return String(modelLineNumber); @@ -144,6 +139,45 @@ export class LineNumbersOverlay extends DynamicViewOverlay { const visibleEndLineNumber = ctx.visibleRange.endLineNumber; const common = '
'; + let relativeLineNumbers: number[] | null = null; + if (this._renderLineNumbers === RenderLineNumbersType.Relative) { + relativeLineNumbers = new Array(visibleEndLineNumber - visibleStartLineNumber + 1); + + if (this._lastCursorViewPosition.lineNumber >= visibleStartLineNumber && this._lastCursorViewPosition.lineNumber <= visibleEndLineNumber) { + relativeLineNumbers[this._lastCursorViewPosition.lineNumber - visibleStartLineNumber] = this._lastCursorModelPosition.lineNumber; + } + + // Iterate up to compute relative line numbers + { + let value = 0; + for (let lineNumber = this._lastCursorViewPosition.lineNumber + 1; lineNumber <= visibleEndLineNumber; lineNumber++) { + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)); + const isWrappedLine = (modelPosition.column !== 1); + if (!isWrappedLine) { + value++; + } + if (lineNumber >= visibleStartLineNumber) { + relativeLineNumbers[lineNumber - visibleStartLineNumber] = isWrappedLine ? 0 : value; + } + } + } + + // Iterate down to compute relative line numbers + { + let value = 0; + for (let lineNumber = this._lastCursorViewPosition.lineNumber - 1; lineNumber >= visibleStartLineNumber; lineNumber--) { + const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 1)); + const isWrappedLine = (modelPosition.column !== 1); + if (!isWrappedLine) { + value++; + } + if (lineNumber <= visibleEndLineNumber) { + relativeLineNumbers[lineNumber - visibleStartLineNumber] = isWrappedLine ? 0 : value; + } + } + } + } + const lineCount = this._context.viewModel.getLineCount(); const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { @@ -157,7 +191,20 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } } - const renderLineNumber = this._getLineRenderLineNumber(lineNumber); + let renderLineNumber: string; + if (relativeLineNumbers) { + const relativeLineNumber = relativeLineNumbers[lineIndex]; + if (this._lastCursorViewPosition.lineNumber === lineNumber) { + // current line! + renderLineNumber = `${relativeLineNumber}`; + } else if (relativeLineNumber) { + renderLineNumber = String(relativeLineNumber); + } else { + renderLineNumber = ''; + } + } else { + renderLineNumber = this._getLineRenderLineNumber(lineNumber); + } if (renderLineNumber) { if (lineNumber === this._activeLineNumber) { diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index e0ff5f222c4..6b917bcfad6 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -116,6 +116,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _horizontalRevealRequest: HorizontalRevealRequest | null; private readonly _lastRenderedData: LastRenderedData; + // Sticky Scroll + private _stickyScrollEnabled: boolean; + private _maxNumberStickyLines: number; + constructor(context: ViewContext, linesContent: FastDomNode) { super(context); this._linesContent = linesContent; @@ -155,6 +159,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._lastRenderedData = new LastRenderedData(); this._horizontalRevealRequest = null; + + // sticky scroll widget + this._stickyScrollEnabled = options.get(EditorOption.experimental).stickyScroll.enabled; + this._maxNumberStickyLines = options.get(EditorOption.experimental).stickyScroll.maxLineCount; } public override dispose(): void { @@ -196,6 +204,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._cursorSurroundingLines = options.get(EditorOption.cursorSurroundingLines); this._cursorSurroundingLinesStyle = options.get(EditorOption.cursorSurroundingLinesStyle); this._canUseLayerHinting = !options.get(EditorOption.disableLayerHinting); + + // sticky scroll + this._stickyScrollEnabled = options.get(EditorOption.experimental).stickyScroll.enabled; + this._maxNumberStickyLines = options.get(EditorOption.experimental).stickyScroll.maxLineCount; + applyFontInfo(this.domNode, fontInfo); this._onOptionsMaybeChanged(); @@ -667,22 +680,30 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default'; + let paddingTop: number = 0; + let paddingBottom: number = 0; + if (!shouldIgnoreScrollOff) { const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines); - boxStartY -= context * this._lineHeight; - boxEndY += Math.max(0, (context - 1)) * this._lineHeight; + if (this._stickyScrollEnabled) { + paddingTop = Math.max(context, this._maxNumberStickyLines) * this._lineHeight; + } else { + paddingTop = context * this._lineHeight; + } + paddingBottom = Math.max(0, (context - 1)) * this._lineHeight; } else { if (!minimalReveal) { // Reveal one more line above (this case is hit when dragging) - boxStartY -= this._lineHeight; + paddingTop = this._lineHeight; } } - if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === viewEvents.VerticalRevealType.Bottom) { // Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom - boxEndY += (minimalReveal ? this._horizontalScrollbarHeight : this._lineHeight); + paddingBottom += (minimalReveal ? this._horizontalScrollbarHeight : this._lineHeight); } + boxStartY -= paddingTop; + boxEndY += paddingBottom; let newScrollTop: number; if (boxEndY - boxStartY > viewportHeight) { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.css b/src/vs/editor/browser/viewParts/minimap/minimap.css index f4663ece04d..f8c0b70f309 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.css +++ b/src/vs/editor/browser/viewParts/minimap/minimap.css @@ -30,3 +30,12 @@ left: -1px; width: 1px; } + +/* 0.5s fade in/out for the minimap */ +.minimap.autohide { + opacity: 0.0; + transition: opacity 0.5s; +} +.minimap.autohide:hover { + opacity: 1.0; +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 2edb031b356..12d68b9b527 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -56,6 +56,8 @@ class MinimapOptions { public readonly showSlider: 'always' | 'mouseover'; + public readonly autohide: boolean; + public readonly pixelRatio: number; public readonly typicalHalfwidthCharacterWidth: number; @@ -120,6 +122,7 @@ class MinimapOptions { this.minimapHeightIsEditorHeight = minimapLayout.minimapHeightIsEditorHeight; this.scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); this.showSlider = minimapOpts.showSlider; + this.autohide = minimapOpts.autohide; this.pixelRatio = pixelRatio; this.typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth; this.lineHeight = options.get(EditorOption.lineHeight); @@ -166,6 +169,7 @@ class MinimapOptions { && this.minimapHeightIsEditorHeight === other.minimapHeightIsEditorHeight && this.scrollBeyondLastLine === other.scrollBeyondLastLine && this.showSlider === other.showSlider + && this.autohide === other.autohide && this.pixelRatio === other.pixelRatio && this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth && this.lineHeight === other.lineHeight @@ -1017,7 +1021,7 @@ export class Minimap extends ViewPart implements IMinimapModel { } else { visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.viewModel.getLineMaxColumn(endLineNumber)); } - const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange); + const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange, true); if (this._samplingState) { const result: ViewModelDecoration[] = []; @@ -1255,10 +1259,17 @@ class InnerMinimap extends Disposable { } private _getMinimapDomNodeClassName(): string { + const class_ = ['minimap']; if (this._model.options.showSlider === 'always') { - return 'minimap slider-always'; + class_.push('slider-always'); + } else { + class_.push('slider-mouseover'); } - return 'minimap slider-mouseover'; + if (this._model.options.autohide) { + class_.push('autohide'); + } + + return class_.join(' '); } public getDomNode(): FastDomNode { @@ -1325,15 +1336,11 @@ class InnerMinimap extends Disposable { return false; } public onLinesDeleted(deleteFromLineNumber: number, deleteToLineNumber: number): boolean { - if (this._lastRenderData) { - this._lastRenderData.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); - } + this._lastRenderData?.onLinesDeleted(deleteFromLineNumber, deleteToLineNumber); return true; } public onLinesInserted(insertFromLineNumber: number, insertToLineNumber: number): boolean { - if (this._lastRenderData) { - this._lastRenderData.onLinesInserted(insertFromLineNumber, insertToLineNumber); - } + this._lastRenderData?.onLinesInserted(insertFromLineNumber, insertToLineNumber); return true; } public onScrollChanged(): boolean { diff --git a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts index 4a530cf6b8a..7e295754924 100644 --- a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts +++ b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts @@ -61,7 +61,7 @@ export class ScrollDecorationViewPart extends ViewPart { if (layoutInfo.minimap.renderMinimap === 0 || (layoutInfo.minimap.minimapWidth > 0 && layoutInfo.minimap.minimapLeft === 0)) { this._width = layoutInfo.width; } else { - this._width = layoutInfo.width - layoutInfo.minimap.minimapWidth - layoutInfo.verticalScrollbarWidth; + this._width = layoutInfo.width - layoutInfo.verticalScrollbarWidth; } } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 5d09d9920fa..d362663580b 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -172,7 +172,13 @@ export class ViewCursor { } const range = firstVisibleRangeForCharacter.ranges[0]; - const width = range.width < 1 ? this._typicalHalfwidthCharacterWidth : range.width; + const width = ( + nextGrapheme === '\t' + ? this._typicalHalfwidthCharacterWidth + : (range.width < 1 + ? this._typicalHalfwidthCharacterWidth + : range.width) + ); let textContentClassName = ''; if (this._cursorStyle === TextEditorCursorStyle.Block) { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 1a4601d7434..8a49db0edb7 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -267,7 +267,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private _bannerDomNode: HTMLElement | null = null; - private _dropIntoEditorDecorationIds: string[] = []; + private _dropIntoEditorDecorations: EditorDecorationsCollection = this.createDecorationsCollection(); constructor( domElement: HTMLElement, @@ -368,10 +368,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._actions[internalAction.id] = internalAction; }); + const isDropIntoEnabled = () => { + return !this._configuration.options.get(EditorOption.readOnly) + && this._configuration.options.get(EditorOption.dropIntoEditor).enabled; + }; + this._register(new dom.DragAndDropObserver(this._domElement, { onDragEnter: () => undefined, onDragOver: e => { - if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + if (!isDropIntoEnabled()) { return; } @@ -381,7 +386,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } }, onDrop: async e => { - if (!this._configuration.options.get(EditorOption.enableDropIntoEditor)) { + if (!isDropIntoEnabled()) { return; } @@ -564,33 +569,47 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.viewModel.viewLayout.getWhitespaces(); } - private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number): number { + private static _getVerticalOffsetAfterPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean): number { const modelPosition = modelData.model.validatePosition({ lineNumber: modelLineNumber, column: modelColumn }); const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); - return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); + return modelData.viewModel.viewLayout.getVerticalOffsetAfterLineNumber(viewPosition.lineNumber, includeViewZones); } - public getTopForLineNumber(lineNumber: number): number { + public getTopForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1, includeViewZones); } public getTopForPosition(lineNumber: number, column: number): number { if (!this._modelData) { return -1; } - return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column); + return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column, false); } - public setHiddenAreas(ranges: IRange[]): void { - if (this._modelData) { - this._modelData.viewModel.setHiddenAreas(ranges.map(r => Range.lift(r))); + private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean = false): number { + const modelPosition = modelData.model.validatePosition({ + lineNumber: modelLineNumber, + column: modelColumn + }); + const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); + return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber, includeViewZones); + } + + public getBottomForLineNumber(lineNumber: number, includeViewZones: boolean = false): number { + if (!this._modelData) { + return -1; } + return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, 1, includeViewZones); + } + + public setHiddenAreas(ranges: IRange[], source?: unknown): void { + this._modelData?.viewModel.setHiddenAreas(ranges.map(r => Range.lift(r)), source); } public getVisibleColumnFromPosition(rawPosition: IPosition): number { @@ -1163,9 +1182,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData || text.length === 0) { return; } - const startPosition = this._modelData.viewModel.getSelection().getStartPosition(); - this._modelData.viewModel.paste(text, pasteOnNewLine, multicursorText, source); - const endPosition = this._modelData.viewModel.getSelection().getStartPosition(); + const viewModel = this._modelData.viewModel; + const startPosition = viewModel.getSelection().getStartPosition(); + viewModel.paste(text, pasteOnNewLine, multicursorText, source); + const endPosition = viewModel.getSelection().getStartPosition(); if (source === 'keyboard') { this._onDidPaste.fire({ range: new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column), @@ -1288,6 +1308,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.getDecorationsInRange(range, this._id, filterValidationDecorations(this._configuration.options)); } + /** + * @deprecated + */ public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { if (!this._modelData) { return []; @@ -1300,7 +1323,17 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.model.deltaDecorations(oldDecorations, newDecorations, this._id); } - public setDecorations(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { + public removeDecorations(decorationIds: string[]): void { + if (!this._modelData || decorationIds.length === 0) { + return; + } + + this._modelData.model.changeDecorations((changeAccessor) => { + changeAccessor.deltaDecorations(decorationIds, []); + }); + } + + public setDecorationsByType(description: string, decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { const newDecorationsSubTypes: { [key: string]: boolean } = {}; const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1342,7 +1375,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void { + public setDecorationsByTypeFast(decorationTypeKey: string, ranges: IRange[]): void { // remove decoration sub types that are no longer used, deregister decoration type if necessary const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; @@ -1362,7 +1395,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } - public removeDecorations(decorationTypeKey: string): void { + public removeDecorationsByType(decorationTypeKey: string): void { // remove decorations for type and sub type const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; if (oldDecorationsIds) { @@ -1786,9 +1819,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } protected _postDetachModelCleanup(detachedModel: ITextModel | null): void { - if (detachedModel) { - detachedModel.removeAllDecorationsWithOwnerId(this._id); - } + detachedModel?.removeAllDecorationsWithOwnerId(this._id); } private _detachModel(): ITextModel | null { @@ -1838,12 +1869,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE options: CodeEditorWidget.dropIntoEditorDecorationOptions }]; - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, newDecorations); + this._dropIntoEditorDecorations.set(newDecorations); this.revealPosition(position, editorCommon.ScrollType.Immediate); } private removeDropIndicator(): void { - this._dropIntoEditorDecorationIds = this.deltaDecorations(this._dropIntoEditorDecorationIds, []); + this._dropIntoEditorDecorations.clear(); } } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ab0ec218a28..f817ce00c0a 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -6,6 +6,7 @@ import 'vs/css!./media/diffEditor'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; +import * as assert from 'vs/base/common/assert'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState, Orientation } from 'vs/base/browser/ui/sash/sash'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -27,7 +28,6 @@ import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/strin import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; @@ -52,10 +52,12 @@ import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData'; -import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer'; +import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { IDimension } from 'vs/editor/common/core/dimension'; import { isHighContrast } from 'vs/platform/theme/common/theme'; +import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider'; +import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider'; export interface IDiffCodeEditorWidgetOptions { originalEditor?: ICodeEditorWidgetOptions; @@ -114,7 +116,9 @@ class VisualEditorState { this._zonesMap = {}; // (2) Model decorations - this._decorations = editor.deltaDecorations(this._decorations, []); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, []); + }); } public apply(editor: CodeEditorWidget, overviewRuler: editorBrowser.IOverviewRuler | null, newDecorations: IEditorDiffDecorationsWithZones, restoreScrollState: boolean): void { @@ -149,17 +153,15 @@ class VisualEditorState { } }); - if (scrollState) { - scrollState.restore(editor); - } + scrollState?.restore(editor); // decorations - this._decorations = editor.deltaDecorations(this._decorations, newDecorations.decorations); + editor.changeDecorations((changeAccessor) => { + this._decorations = changeAccessor.deltaDecorations(this._decorations, newDecorations.decorations); + }); // overview ruler - if (overviewRuler) { - overviewRuler.setZones(newDecorations.overviewZones); - } + overviewRuler?.setZones(newDecorations.overviewZones); } } @@ -220,7 +222,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _updateDecorationsRunner: RunOnceScheduler; - private readonly _editorWorkerService: IEditorWorkerService; + private readonly _documentDiffProvider: IDocumentDiffProvider; private readonly _contextKeyService: IContextKeyService; private readonly _instantiationService: IInstantiationService; private readonly _codeEditorService: ICodeEditorService; @@ -234,7 +236,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE options: Readonly, codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions, @IClipboardService clipboardService: IClipboardService, - @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -245,7 +246,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE ) { super(); - this._editorWorkerService = editorWorkerService; + this._documentDiffProvider = instantiationService.createInstance(WorkerBasedDocumentDiffProvider); this._codeEditorService = codeEditorService; this._contextKeyService = this._register(contextKeyService.createScoped(domElement)); this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])); @@ -263,6 +264,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._options = validateDiffEditorOptions(options, { enableSplitViewResizing: true, renderSideBySide: true, + renderMarginRevertIcon: true, maxComputationTime: 5000, maxFileSize: 50, ignoreTrimWhitespace: true, @@ -270,7 +272,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE originalEditable: false, diffCodeLens: false, renderOverviewRuler: true, - diffWordWrap: 'inherit' + diffWordWrap: 'inherit', + diffAlgorithm: 'smart', }); if (typeof options.isInEmbeddedEditor !== 'undefined') { @@ -429,24 +432,30 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return result; } - private _recreateOverviewRulers(): void { + private _disposeOverviewRulers(): void { + if (this._originalOverviewRuler) { + this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode()); + this._originalOverviewRuler.dispose(); + this._originalOverviewRuler = null; + } + if (this._modifiedOverviewRuler) { + this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode()); + this._modifiedOverviewRuler.dispose(); + this._modifiedOverviewRuler = null; + } + } + + private _createOverviewRulers(): void { if (!this._options.renderOverviewRuler) { return; } - if (this._originalOverviewRuler) { - this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode()); - this._originalOverviewRuler.dispose(); - } + assert.ok(!this._originalOverviewRuler && !this._modifiedOverviewRuler); + if (this._originalEditor.hasModel()) { this._originalOverviewRuler = this._originalEditor.createOverviewRuler('original diffOverviewRuler')!; this._overviewDomElement.appendChild(this._originalOverviewRuler.getDomNode()); } - - if (this._modifiedOverviewRuler) { - this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode()); - this._modifiedOverviewRuler.dispose(); - } if (this._modifiedEditor.hasModel()) { this._modifiedOverviewRuler = this._modifiedEditor.createOverviewRuler('modified diffOverviewRuler')!; this._overviewDomElement.appendChild(this._modifiedOverviewRuler.getDomNode()); @@ -643,7 +652,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE range, text: '', }]); - } else if (change.modifiedEndLineNumber === 0 && originalContent) { + } else if (change.modifiedEndLineNumber === 0 && originalContent !== null) { // Delete change. // To revert: insert the old content and a linebreak. @@ -742,8 +751,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const changed = changedDiffEditorOptions(this._options, newOptions); this._options = newOptions; - const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators); - const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize)); + const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon); + const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize || changed.diffAlgorithm)); if (beginUpdateDecorations) { this._beginUpdateDecorations(); @@ -794,6 +803,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE // Remove all view zones & decorations this._cleanViewZonesAndDecorations(); + this._disposeOverviewRulers(); + // Update code editor models this._originalEditor.setModel(model ? model.original : null); this._modifiedEditor.setModel(model ? model.modified : null); @@ -812,7 +823,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setState(editorBrowser.DiffEditorState.Idle); if (model) { - this._recreateOverviewRulers(); + this._createOverviewRulers(); // Begin comparing this._beginUpdateDecorations(); @@ -1101,13 +1112,65 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } this._setState(editorBrowser.DiffEditorState.ComputingDiff); - this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._options.ignoreTrimWhitespace, this._options.maxComputationTime).then((result) => { + this._documentDiffProvider.computeDiff(currentOriginalModel, currentModifiedModel, { + ignoreTrimWhitespace: this._options.ignoreTrimWhitespace, + maxComputationTime: this._options.maxComputationTime, + diffAlgorithm: this._options.diffAlgorithm, + }).then(result => { if (currentToken === this._diffComputationToken && currentOriginalModel === this._originalEditor.getModel() && currentModifiedModel === this._modifiedEditor.getModel() ) { this._setState(editorBrowser.DiffEditorState.DiffComputed); - this._diffComputationResult = result; + this._diffComputationResult = { + identical: result.identical, + quitEarly: result.quitEarly, + changes: result.changes.map(m => { + // TODO don't do this translation, but use the diff result directly + let originalStartLineNumber: number; + let originalEndLineNumber: number; + let modifiedStartLineNumber: number; + let modifiedEndLineNumber: number; + let innerChanges = m.innerChanges; + + if (m.originalRange.isEmpty) { + // Insertion + originalStartLineNumber = m.originalRange.startLineNumber - 1; + originalEndLineNumber = 0; + innerChanges = undefined; + } else { + originalStartLineNumber = m.originalRange.startLineNumber; + originalEndLineNumber = m.originalRange.endLineNumberExclusive - 1; + } + + if (m.modifiedRange.isEmpty) { + // Deletion + modifiedStartLineNumber = m.modifiedRange.startLineNumber - 1; + modifiedEndLineNumber = 0; + innerChanges = undefined; + } else { + modifiedStartLineNumber = m.modifiedRange.startLineNumber; + modifiedEndLineNumber = m.modifiedRange.endLineNumberExclusive - 1; + } + + return { + originalStartLineNumber, + originalEndLineNumber, + modifiedStartLineNumber, + modifiedEndLineNumber, + charChanges: innerChanges?.map(m => ({ + originalStartLineNumber: m.originalRange.startLineNumber, + originalStartColumn: m.originalRange.startColumn, + originalEndLineNumber: m.originalRange.endLineNumber, + originalEndColumn: m.originalRange.endColumn, + modifiedStartLineNumber: m.modifiedRange.startLineNumber, + modifiedStartColumn: m.modifiedRange.startColumn, + modifiedEndLineNumber: m.modifiedRange.endLineNumber, + modifiedEndColumn: m.modifiedRange.endColumn, + })) + }; + }) + }; this._updateDecorationsRunner.schedule(); this._onDidUpdateDiff.fire(); } @@ -1138,7 +1201,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE const foreignOriginal = this._originalEditorState.getForeignViewZones(this._originalEditor.getWhitespaces()); const foreignModified = this._modifiedEditorState.getForeignViewZones(this._modifiedEditor.getWhitespaces()); - const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._options.ignoreTrimWhitespace, this._options.renderIndicators, foreignOriginal, foreignModified); + const diffDecorations = this._strategy.getEditorsDiffDecorations(lineChanges, this._options.ignoreTrimWhitespace, this._options.renderIndicators, this._options.renderMarginRevertIcon, foreignOriginal, foreignModified); try { this._currentlyChangingViewZones = true; @@ -1179,7 +1242,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE result.ariaLabel = options.originalAriaLabel; } result.readOnly = !this._options.originalEditable; - result.enableDropIntoEditor = !result.readOnly; + result.dropIntoEditor = { enabled: !result.readOnly }; result.extraEditorClassName = 'original-in-monaco-diff-editor'; return { ...result, @@ -1314,9 +1377,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _setStrategy(newStrategy: DiffEditorWidgetStyle): void { - if (this._strategy) { - this._strategy.dispose(); - } + this._strategy?.dispose(); this._strategy = newStrategy; newStrategy.applyColors(this._themeService.getColorTheme()); @@ -1454,7 +1515,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { return hasChanges; } - public getEditorsDiffDecorations(lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[]): IEditorsDiffDecorationsWithZones { + public getEditorsDiffDecorations(lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean, originalWhitespaces: IEditorWhitespace[], modifiedWhitespaces: IEditorWhitespace[]): IEditorsDiffDecorationsWithZones { // Get view zones modifiedWhitespaces = modifiedWhitespaces.sort((a, b) => { return a.afterLineNumber - b.afterLineNumber; @@ -1466,7 +1527,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { // Get decorations & overview ruler zones const originalDecorations = this._getOriginalEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators); - const modifiedDecorations = this._getModifiedEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators); + const modifiedDecorations = this._getModifiedEditorDecorations(zones, lineChanges, ignoreTrimWhitespace, renderIndicators, renderMarginRevertIcon); return { original: { @@ -1484,7 +1545,7 @@ abstract class DiffEditorWidgetStyle extends Disposable { protected abstract _getViewZones(lineChanges: ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], renderIndicators: boolean): IEditorsZones; protected abstract _getOriginalEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; - protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations; + protected abstract _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations; public abstract setEnableSplitViewResizing(enableSplitViewResizing: boolean): void; public abstract layout(): number; @@ -2026,7 +2087,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti return result; } - protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations { const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); @@ -2041,15 +2102,17 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IVerti for (const lineChange of lineChanges) { // Arrows for reverting changes. - if (lineChange.modifiedEndLineNumber > 0) { - result.decorations.push({ - range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber, 1), - options: DECORATIONS.arrowRevertChange - }); - } else { - const viewZone = zones.modified.find(z => z.afterLineNumber === lineChange.modifiedStartLineNumber); - if (viewZone) { - viewZone.marginDomNode = createViewZoneMarginArrow(); + if (renderMarginRevertIcon) { + if (lineChange.modifiedEndLineNumber > 0) { + result.decorations.push({ + range: new Range(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedStartLineNumber, 1), + options: DECORATIONS.arrowRevertChange + }); + } else { + const viewZone = zones.modified.find(z => z.afterLineNumber === lineChange.modifiedStartLineNumber); + if (viewZone) { + viewZone.marginDomNode = createViewZoneMarginArrow(); + } } } @@ -2217,7 +2280,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle { return result; } - protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean): IEditorDiffDecorations { + protected _getModifiedEditorDecorations(zones: IEditorsZones, lineChanges: ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, renderMarginRevertIcon: boolean): IEditorDiffDecorations { const modifiedEditor = this._dataSource.getModifiedEditor(); const overviewZoneColor = String(this._insertColor); @@ -2636,6 +2699,7 @@ function validateDiffEditorOptions(options: Readonly, defaul return { enableSplitViewResizing: validateBooleanOption(options.enableSplitViewResizing, defaults.enableSplitViewResizing), renderSideBySide: validateBooleanOption(options.renderSideBySide, defaults.renderSideBySide), + renderMarginRevertIcon: validateBooleanOption(options.renderMarginRevertIcon, defaults.renderMarginRevertIcon), maxComputationTime: clampedInt(options.maxComputationTime, defaults.maxComputationTime, 0, Constants.MAX_SAFE_SMALL_INTEGER), maxFileSize: clampedInt(options.maxFileSize, defaults.maxFileSize, 0, Constants.MAX_SAFE_SMALL_INTEGER), ignoreTrimWhitespace: validateBooleanOption(options.ignoreTrimWhitespace, defaults.ignoreTrimWhitespace), @@ -2644,6 +2708,7 @@ function validateDiffEditorOptions(options: Readonly, defaul diffCodeLens: validateBooleanOption(options.diffCodeLens, defaults.diffCodeLens), renderOverviewRuler: validateBooleanOption(options.renderOverviewRuler, defaults.renderOverviewRuler), diffWordWrap: validateDiffWordWrap(options.diffWordWrap, defaults.diffWordWrap), + diffAlgorithm: validateStringSetOption(options.diffAlgorithm, defaults.diffAlgorithm, ['smart', 'experimental']), }; } @@ -2651,6 +2716,7 @@ function changedDiffEditorOptions(a: ValidDiffEditorBaseOptions, b: ValidDiffEdi return { enableSplitViewResizing: (a.enableSplitViewResizing !== b.enableSplitViewResizing), renderSideBySide: (a.renderSideBySide !== b.renderSideBySide), + renderMarginRevertIcon: (a.renderMarginRevertIcon !== b.renderMarginRevertIcon), maxComputationTime: (a.maxComputationTime !== b.maxComputationTime), maxFileSize: (a.maxFileSize !== b.maxFileSize), ignoreTrimWhitespace: (a.ignoreTrimWhitespace !== b.ignoreTrimWhitespace), @@ -2659,6 +2725,7 @@ function changedDiffEditorOptions(a: ValidDiffEditorBaseOptions, b: ValidDiffEdi diffCodeLens: (a.diffCodeLens !== b.diffCodeLens), renderOverviewRuler: (a.renderOverviewRuler !== b.renderOverviewRuler), diffWordWrap: (a.diffWordWrap !== b.diffWordWrap), + diffAlgorithm: (a.diffAlgorithm !== b.diffAlgorithm), }; } diff --git a/src/vs/editor/browser/widget/diffNavigator.ts b/src/vs/editor/browser/widget/diffNavigator.ts index 525c143d10e..6c4cd6f02e2 100644 --- a/src/vs/editor/browser/widget/diffNavigator.ts +++ b/src/vs/editor/browser/widget/diffNavigator.ts @@ -10,7 +10,7 @@ import * as objects from 'vs/base/common/objects'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; import { Range } from 'vs/editor/common/core/range'; -import { ILineChange } from 'vs/editor/common/diff/diffComputer'; +import { ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { ScrollType } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 38ae5e81a32..cb0cb1f935b 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -34,7 +34,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ILanguageIdCodec } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ILineChange } from 'vs/editor/common/diff/diffComputer'; +import { ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; const DIFF_LINES_PADDING = 3; @@ -846,9 +846,7 @@ class DiffReviewNext extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor) { - diffEditor.diffReviewNext(); - } + diffEditor?.diffReviewNext(); } } @@ -869,9 +867,7 @@ class DiffReviewPrev extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); - if (diffEditor) { - diffEditor.diffReviewPrev(); - } + diffEditor?.diffReviewPrev(); } } diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 856c957a875..1d24e5e5887 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -9,7 +9,6 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { ConfigurationChangedEvent, IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -76,7 +75,6 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { domElement: HTMLElement, options: Readonly, parentEditor: ICodeEditor, - @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -86,7 +84,7 @@ export class EmbeddedDiffEditorWidget extends DiffEditorWidget { @IClipboardService clipboardService: IClipboardService, @IEditorProgressService editorProgressService: IEditorProgressService, ) { - super(domElement, parentEditor.getRawOptions(), {}, clipboardService, editorWorkerService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); + super(domElement, parentEditor.getRawOptions(), {}, clipboardService, contextKeyService, instantiationService, codeEditorService, themeService, notificationService, contextMenuService, editorProgressService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts new file mode 100644 index 00000000000..a05bdb7224b --- /dev/null +++ b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LineRange, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; +import { Range } from 'vs/editor/common/core/range'; +import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; +import { ITextModel } from 'vs/editor/common/model'; + +export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider { + constructor( + @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, + ) { + } + + async computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise { + const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options); + if (!result) { + throw new Error('no diff result available'); + } + + // Convert from space efficient JSON data to rich objects. + const diff: IDocumentDiff = { + identical: result.identical, + quitEarly: result.quitEarly, + changes: result.changes.map( + (c) => + new LineRangeMapping( + new LineRange(c[0], c[1]), + new LineRange(c[2], c[3]), + c[4]?.map( + (c) => + new RangeMapping( + new Range(c[0], c[1], c[2], c[3]), + new Range(c[4], c[5], c[6], c[7]) + ) + ) + ) + ), + }; + return diff; + } +} diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index 650f20b7047..7843b0a1640 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -24,7 +24,7 @@ const editorConfiguration: IConfigurationNode = { type: 'number', default: EDITOR_MODEL_DEFAULTS.tabSize, minimum: 1, - markdownDescription: nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") + markdownDescription: nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when {0} is on.", '`#editor.detectIndentation#`') }, // 'editor.indentSize': { // 'anyOf': [ @@ -43,12 +43,12 @@ const editorConfiguration: IConfigurationNode = { 'editor.insertSpaces': { type: 'boolean', default: EDITOR_MODEL_DEFAULTS.insertSpaces, - markdownDescription: nls.localize('insertSpaces', "Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") + markdownDescription: nls.localize('insertSpaces', "Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when {0} is on.", '`#editor.detectIndentation#`') }, 'editor.detectIndentation': { type: 'boolean', default: EDITOR_MODEL_DEFAULTS.detectIndentation, - markdownDescription: nls.localize('detectIndentation', "Controls whether `#editor.tabSize#` and `#editor.insertSpaces#` will be automatically detected when a file is opened based on the file contents.") + markdownDescription: nls.localize('detectIndentation', "Controls whether {0} and {1} will be automatically detected when a file is opened based on the file contents.", '`#editor.tabSize#`', '`#editor.insertSpaces#`') }, 'editor.trimAutoWhitespace': { type: 'boolean', @@ -146,6 +146,11 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('sideBySide', "Controls whether the diff editor shows the diff side by side or inline.") }, + 'diffEditor.renderMarginRevertIcon': { + type: 'boolean', + default: true, + description: nls.localize('renderMarginRevertIcon', "When enabled, the diff editor shows arrows in its glyph margin to revert changes.") + }, 'diffEditor.ignoreTrimWhitespace': { type: 'boolean', default: true, @@ -168,9 +173,18 @@ const editorConfiguration: IConfigurationNode = { markdownEnumDescriptions: [ nls.localize('wordWrap.off', "Lines will never wrap."), nls.localize('wordWrap.on', "Lines will wrap at the viewport width."), - nls.localize('wordWrap.inherit', "Lines will wrap according to the `#editor.wordWrap#` setting."), + nls.localize('wordWrap.inherit', "Lines will wrap according to the {0} setting.", '`#editor.wordWrap#`'), ] - } + }, + 'diffEditor.diffAlgorithm': { + type: 'string', + enum: ['smart', 'experimental'], + default: 'smart', + markdownEnumDescriptions: [ + nls.localize('diffAlgorithm.smart', "Uses the default diffing algorithm."), + nls.localize('diffAlgorithm.experimental', "Uses an experimental diffing algorithm."), + ] + }, } }; diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 8b093749129..591d345f5f9 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -169,6 +169,10 @@ export interface IEditorOptions { * Control the behavior and rendering of the scrollbars. */ scrollbar?: IEditorScrollbarOptions; + /** + * Control the behavior of experimental options + */ + experimental?: IEditorExperimentalOptions; /** * Control the behavior and rendering of the minimap. */ @@ -563,7 +567,7 @@ export interface IEditorOptions { * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. * Defaults to 'mouseover'. */ - showFoldingControls?: 'always' | 'mouseover'; + showFoldingControls?: 'always' | 'never' | 'mouseover'; /** * Controls whether clicking on the empty content after a folded line will unfold the line. * Defaults to false. @@ -661,11 +665,11 @@ export interface IEditorOptions { bracketPairColorization?: IBracketPairColorizationOptions; /** - * Enables dropping into the editor from an external source. + * Controls dropping into the editor from an external source. * - * This shows a preview of the drop location and triggers an `onDropIntoEditor` event. + * When enabled, this shows a preview of the drop location and triggers an `onDropIntoEditor` event. */ - enableDropIntoEditor?: boolean; + dropIntoEditor?: IDropIntoEditorOptions; } /** @@ -705,6 +709,11 @@ export interface IDiffEditorBaseOptions { * Defaults to true. */ renderIndicators?: boolean; + /** + * Shows icons in the glyph margin to revert changes. + * Default to true. + */ + renderMarginRevertIcon?: boolean; /** * Original model should be editable? * Defaults to false. @@ -724,6 +733,10 @@ export interface IDiffEditorBaseOptions { * Control the wrapping of the diff editor. */ diffWordWrap?: 'off' | 'on' | 'inherit'; + /** + * Diff Algorithm + */ + diffAlgorithm?: 'smart' | 'experimental'; } /** @@ -818,7 +831,7 @@ export interface IEditorOption { /** * Might modify `value`. */ - applyUpdate(value: V, update: V): ApplyUpdateResult; + applyUpdate(value: V | undefined, update: V): ApplyUpdateResult; } /** @@ -847,7 +860,7 @@ abstract class BaseEditorOption implements IEditor this.schema = schema; } - public applyUpdate(value: V, update: V): ApplyUpdateResult { + public applyUpdate(value: V | undefined, update: V): ApplyUpdateResult { return applyUpdate(value, update); } @@ -865,13 +878,13 @@ export class ApplyUpdateResult { ) { } } -function applyUpdate(value: T, update: T): ApplyUpdateResult { +function applyUpdate(value: T | undefined, update: T): ApplyUpdateResult { if (typeof value !== 'object' || typeof update !== 'object' || !value || !update) { return new ApplyUpdateResult(update, value !== update); } if (Array.isArray(value) || Array.isArray(update)) { const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); - return new ApplyUpdateResult(update, arrayEquals); + return new ApplyUpdateResult(update, !arrayEquals); } let didChange = false; for (const key in update) { @@ -902,7 +915,7 @@ abstract class ComputedEditorOption implements IEdito this.defaultValue = undefined; } - public applyUpdate(value: V, update: V): ApplyUpdateResult { + public applyUpdate(value: V | undefined, update: V): ApplyUpdateResult { return applyUpdate(value, update); } @@ -927,7 +940,7 @@ class SimpleEditorOption implements IEditorOption { + public applyUpdate(value: V | undefined, update: V): ApplyUpdateResult { return applyUpdate(value, update); } @@ -2326,6 +2339,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption { + + constructor() { + const defaults: EditorExperimentalOptions = { stickyScroll: { enabled: false, maxLineCount: 5 } }; + super( + EditorOption.experimental, 'experimental', defaults, + { + 'editor.experimental.stickyScroll.enabled': { + type: 'boolean', + default: defaults.stickyScroll.enabled, + description: nls.localize('editor.experimental.stickyScroll', "Shows the nested current scopes during the scroll at the top of the editor.") + }, + 'editor.experimental.stickyScroll.maxLineCount': { + type: 'number', + default: defaults.stickyScroll.maxLineCount, + minimum: 1, + maximum: 10, + description: nls.localize('editor.experimental.stickyScroll.', "Defines the maximum number of sticky lines to show.") + }, + } + ); + } + + public validate(_input: any): EditorExperimentalOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IEditorExperimentalOptions; + return { + stickyScroll: { + enabled: boolean(input.stickyScroll?.enabled, this.defaultValue.stickyScroll.enabled), + maxLineCount: EditorIntOption.clampedInt(input.stickyScroll?.maxLineCount, this.defaultValue.stickyScroll.maxLineCount, 1, 10), + } + }; + } +} + +//#endregion + //#region inlayHints /** @@ -2556,12 +2631,12 @@ class EditorInlayHints extends BaseEditorOption